From f07da7158c441bbe2366c57a820ee7bd0b0d6249 Mon Sep 17 00:00:00 2001 From: Patrice Bouillet Date: Fri, 17 Jul 2015 09:38:07 +0200 Subject: [PATCH] INSPECTIT-1944: initial commit for going open source --- .gitignore | 4 + Agent/.checkstyle | 14 + Agent/.classpath | 14 + Agent/.gitignore | 11 + Agent/.pmd | 7 + Agent/.project | 43 + .../.settings/org.apache.ivyde.eclipse.prefs | 2 + Agent/.settings/org.eclipse.jdt.core.prefs | 14 + Agent/META-INF/MANIFEST.MF | 2 + Agent/build.properties | 46 + Agent/build.xml | 261 ++ Agent/config/common/ejb.cfg | 18 + Agent/config/common/exclude-classes.cfg | 25 + Agent/config/common/hibernate.cfg | 20 + Agent/config/common/http.cfg | 21 + Agent/config/common/jpa.cfg | 10 + Agent/config/common/jsf.cfg | 27 + Agent/config/common/jta.cfg | 19 + Agent/config/common/sql-parameters.cfg | 89 + Agent/config/common/sql.cfg | 58 + Agent/config/common/struts.cfg | 12 + Agent/config/common/webservice.cfg | 7 + Agent/config/inspectit-agent.cfg | 113 + Agent/config/logging.properties | 17 + Agent/logging-config.xml | 90 + Agent/resources/ivy/ivy.xml | 29 + Agent/resources/testng/testng.xml | 104 + Agent/src/config/bytebufferpool.properties | 17 + .../info/novatec/inspectit/agent/Agent.java | 26 + .../info/novatec/inspectit/agent/IAgent.java | 50 + .../novatec/inspectit/agent/SpringAgent.java | 200 ++ .../agent/analyzer/IByteCodeAnalyzer.java | 28 + .../agent/analyzer/IClassPoolAnalyzer.java | 55 + .../agent/analyzer/IInheritanceAnalyzer.java | 83 + .../agent/analyzer/IMatchPattern.java | 30 + .../inspectit/agent/analyzer/IMatcher.java | 74 + .../agent/analyzer/impl/AbstractMatcher.java | 40 + .../analyzer/impl/AnnotationMatcher.java | 237 ++ .../agent/analyzer/impl/ByteCodeAnalyzer.java | 394 +++ .../analyzer/impl/ClassPoolAnalyzer.java | 162 ++ .../agent/analyzer/impl/DirectMatcher.java | 122 + .../agent/analyzer/impl/IndirectMatcher.java | 170 ++ .../analyzer/impl/InheritanceAnalyzer.java | 238 ++ .../agent/analyzer/impl/InterfaceMatcher.java | 98 + .../agent/analyzer/impl/ModifierMatcher.java | 167 ++ .../analyzer/impl/SimpleMatchPattern.java | 167 ++ .../analyzer/impl/SuperclassMatcher.java | 98 + .../agent/analyzer/impl/ThrowableMatcher.java | 117 + .../agent/buffer/AbstractBufferStrategy.java | 29 + .../agent/buffer/IBufferStrategy.java | 35 + .../buffer/impl/SimpleBufferStrategy.java | 95 + .../agent/buffer/impl/SizeBufferStrategy.java | 118 + .../agent/config/IConfigurationReader.java | 20 + .../agent/config/IConfigurationStorage.java | 277 ++ .../agent/config/IPropertyAccessor.java | 56 + .../agent/config/ParserException.java | 47 + .../inspectit/agent/config/PriorityEnum.java | 29 + .../agent/config/PropertyAccessException.java | 48 + .../agent/config/StorageException.java | 47 + .../config/impl/AbstractSensorConfig.java | 224 ++ .../config/impl/AbstractSensorTypeConfig.java | 117 + .../config/impl/ConfigurationStorage.java | 749 +++++ .../config/impl/FileConfigurationReader.java | 585 ++++ .../config/impl/MethodSensorTypeConfig.java | 91 + .../config/impl/PlatformSensorTypeConfig.java | 19 + .../agent/config/impl/PropertyAccessor.java | 411 +++ .../config/impl/RegisteredSensorConfig.java | 339 +++ .../agent/config/impl/RepositoryConfig.java | 53 + .../agent/config/impl/StrategyConfig.java | 55 + .../config/impl/UnregisteredSensorConfig.java | 337 +++ .../connection/AbstractRemoteMethodCall.java | 106 + .../agent/connection/IConnection.java | 149 + .../connection/RegistrationException.java | 46 + .../agent/connection/RetryException.java | 36 + .../agent/connection/RetryStrategy.java | 87 + .../ServerUnavailableException.java | 35 + .../agent/connection/impl/AddDataObjects.java | 61 + .../impl/AddSensorTypeToMethod.java | 65 + .../impl/AdditiveWaitRetryStrategy.java | 79 + .../impl/ExponentialBackoffRetryStrategy.java | 61 + .../connection/impl/KryoNetConnection.java | 357 +++ .../connection/impl/RegisterMethodIdent.java | 71 + .../impl/RegisterMethodSensorType.java | 68 + .../impl/RegisterPlatformSensorType.java | 67 + .../inspectit/agent/core/ICoreService.java | 145 + .../inspectit/agent/core/IIdManager.java | 115 + .../inspectit/agent/core/IObjectStorage.java | 22 + .../agent/core/IdNotAvailableException.java | 50 + .../inspectit/agent/core/ListListener.java | 24 + .../agent/core/impl/CoreService.java | 662 +++++ .../inspectit/agent/core/impl/IdManager.java | 742 +++++ .../agent/hooking/IConstructorHook.java | 50 + .../inspectit/agent/hooking/IHook.java | 9 + .../agent/hooking/IHookDispatcher.java | 123 + .../agent/hooking/IHookDispatcherMapper.java | 33 + .../agent/hooking/IHookInstrumenter.java | 59 + .../inspectit/agent/hooking/IMethodHook.java | 75 + .../agent/hooking/impl/HookDispatcher.java | 538 ++++ .../agent/hooking/impl/HookException.java | 36 + .../agent/hooking/impl/HookInstrumenter.java | 364 +++ .../inspectit/agent/javaagent/JavaAgent.java | 515 ++++ .../inspectit/agent/jrebel/JRebelUtil.java | 105 + .../agent/logback/LogInitializer.java | 138 + .../sending/AbstractSendingStrategy.java | 79 + .../agent/sending/ISendingStrategy.java | 39 + .../agent/sending/impl/ListSizeStrategy.java | 59 + .../agent/sending/impl/TimeStrategy.java | 102 + .../inspectit/agent/sensor/ISensor.java | 23 + .../sensor/exception/ExceptionSensor.java | 60 + .../sensor/exception/ExceptionSensorHook.java | 294 ++ .../sensor/exception/IExceptionSensor.java | 13 + .../exception/IExceptionSensorHook.java | 59 + .../exception/IdentityHashToDataObject.java | 80 + .../sensor/method/AbstractMethodSensor.java | 37 + .../agent/sensor/method/IMethodSensor.java | 23 + .../method/averagetimer/AverageTimerHook.java | 166 ++ .../averagetimer/AverageTimerSensor.java | 81 + .../agent/sensor/method/http/HttpHook.java | 351 +++ .../http/HttpRequestParameterExtractor.java | 438 +++ .../agent/sensor/method/http/HttpSensor.java | 70 + .../sensor/method/http/StartEndMarker.java | 111 + .../InvocationSequenceHook.java | 610 +++++ .../InvocationSequenceSensor.java | 92 + .../UnsupportedMethodException.java | 20 + .../sensor/method/jdbc/ConnectionHook.java | 53 + .../method/jdbc/ConnectionMetaDataHook.java | 44 + .../method/jdbc/ConnectionMetaDataSensor.java | 43 + .../jdbc/ConnectionMetaDataStorage.java | 269 ++ .../sensor/method/jdbc/ConnectionSensor.java | 62 + .../method/jdbc/PreparedStatementHook.java | 222 ++ .../jdbc/PreparedStatementParameterHook.java | 101 + .../PreparedStatementParameterSensor.java | 58 + .../method/jdbc/PreparedStatementSensor.java | 68 + .../sensor/method/jdbc/StatementHook.java | 158 ++ .../method/jdbc/StatementReflectionCache.java | 32 + .../sensor/method/jdbc/StatementSensor.java | 67 + .../sensor/method/jdbc/StatementStorage.java | 298 ++ .../method/timer/AggregateTimerStorage.java | 63 + .../sensor/method/timer/ITimerStorage.java | 23 + .../method/timer/OptimizedTimerStorage.java | 72 + .../method/timer/PlainTimerStorage.java | 62 + .../agent/sensor/method/timer/TimerHook.java | 235 ++ .../sensor/method/timer/TimerSensor.java | 82 + .../method/timer/TimerStorageFactory.java | 135 + .../platform/AbstractPlatformSensor.java | 37 + .../platform/ClassLoadingInformation.java | 168 ++ .../platform/CompilationInformation.java | 126 + .../agent/sensor/platform/CpuInformation.java | 131 + .../sensor/platform/IPlatformSensor.java | 34 + .../sensor/platform/MemoryInformation.java | 261 ++ .../sensor/platform/RuntimeInformation.java | 126 + .../sensor/platform/SystemInformation.java | 354 +++ .../sensor/platform/ThreadInformation.java | 189 ++ .../platform/provider/MemoryInfoProvider.java | 38 + .../provider/OperatingSystemInfoProvider.java | 74 + .../provider/PlatformSensorInfoProvider.java | 39 + .../provider/RuntimeInfoProvider.java | 144 + .../platform/provider/ThreadInfoProvider.java | 40 + .../def/DefaultMemoryInfoProvider.java | 37 + .../DefaultOperatingSystemInfoProvider.java | 111 + .../DefaultPlatformSensorInfoProvider.java | 65 + .../def/DefaultRuntimeInfoProvider.java | 161 ++ .../def/DefaultThreadInfoProvider.java | 48 + .../PlatformSensorInfoProviderFactory.java | 103 + .../IbmJava6OperatingSystemInfoProvider.java | 223 ++ .../IbmJava6PlatformSensorInfoProvider.java | 92 + .../sun/SunOperatingSystemInfoProvider.java | 118 + .../sun/SunPlatformSensorInfoProvider.java | 68 + .../provider/util/CpuUsageCalculator.java | 98 + .../agent/spring/PrototypesProvider.java | 64 + .../agent/spring/SpringConfiguration.java | 146 + .../StrategyAndSensorConfiguration.java | 41 + .../util/MessageFormatFormatter.java | 49 + .../inspectit/util/ReflectionCache.java | 91 + .../inspectit/util/StringConstraint.java | 185 ++ .../inspectit/util/ThreadLocalStack.java | 62 + .../info/novatec/inspectit/util/Timer.java | 25 + .../info/novatec/inspectit/util/WeakList.java | 139 + .../inspectit/agent/AbstractLogSupport.java | 37 + .../novatec/inspectit/agent/MockInit.java | 13 + .../agent/analyzer/AnnotationMatcherTest.java | 284 ++ .../agent/analyzer/DirectMatcherTest.java | 158 ++ .../agent/analyzer/IndirectMatcherTest.java | 193 ++ .../agent/analyzer/InterfaceMatcherTest.java | 205 ++ .../agent/analyzer/ModifierMatcherTest.java | 210 ++ .../analyzer/SimpleMatchPatternTest.java | 70 + .../agent/analyzer/SuperclassMatcherTest.java | 205 ++ .../analyzer/classes/AbstractSubTest.java | 6 + .../agent/analyzer/classes/AbstractTest.java | 8 + .../agent/analyzer/classes/EmptyClass.java | 6 + .../analyzer/classes/ExceptionTestClass.java | 59 + .../classes/ExceptionalTestClass.java | 18 + .../agent/analyzer/classes/ISubTest.java | 5 + .../agent/analyzer/classes/ITest.java | 9 + .../agent/analyzer/classes/ITestTwo.java | 5 + .../agent/analyzer/classes/MyTestError.java | 23 + .../analyzer/classes/MyTestException.java | 23 + .../agent/analyzer/classes/TestClass.java | 85 + .../analyzer/classes/TestClassLoader.java | 14 + .../analyzer/impl/ByteCodeAnalyzerTest.java | 408 +++ .../analyzer/impl/ClassPoolAnalyzerTest.java | 125 + .../impl/InheritanceAnalyzerTest.java | 207 ++ .../buffer/impl/SimpleBufferStrategyTest.java | 67 + .../buffer/impl/SizeBufferStrategyTest.java | 97 + .../config/impl/ConfigurationFilesTest.java | 78 + .../config/impl/ConfigurationStorageTest.java | 679 +++++ .../impl/FileConfigurationReaderTest.java | 516 ++++ .../config/impl/PropertyAccessorTest.java | 504 ++++ .../agent/core/impl/CoreServiceTest.java | 386 +++ .../agent/core/impl/IdManagerTest.java | 319 +++ .../hooking/impl/HookDispatcherTest.java | 776 ++++++ .../hooking/impl/HookInstrumenterTest.java | 1392 ++++++++++ .../sending/impl/ListSizeStrategyTest.java | 84 + .../agent/sending/impl/TimeStrategyTest.java | 59 + .../exception/ExceptionSensorHookTest.java | 415 +++ .../averagetimer/AverageTimerHookTest.java | 445 +++ .../sensor/method/http/HttpHookTest.java | 455 +++ .../HttpRequestParameterExtractorTest.java | 304 ++ .../InvocationSequenceHookTest.java | 223 ++ .../jdbc/ConnectionMetaDataExtractorTest.java | 68 + .../jdbc/ConnectionMetaDataHookTest.java | 60 + .../jdbc/ConnectionMetaDataStorageTest.java | 59 + .../method/jdbc/JDBCUrlExtractorTest.java | 47 + .../jdbc/PreparedStatementHookTest.java | 76 + .../PreparedStatementParameterHookTest.java | 205 ++ .../sensor/method/jdbc/StatementHookTest.java | 237 ++ .../method/jdbc/StatementSensorTest.java | 45 + .../method/jdbc/StatementStorageTest.java | 87 + .../sensor/method/timer/TimerHookTest.java | 547 ++++ .../platform/ClassLoadingInformationTest.java | 213 ++ .../platform/CompilationInformationTest.java | 210 ++ .../sensor/platform/CpuInformationTest.java | 208 ++ .../platform/CpuUsageCalculatorTest.java | 71 + .../platform/MemoryInformationTest.java | 320 +++ .../platform/RuntimeInformationTest.java | 171 ++ .../platform/SystemInformationTest.java | 449 +++ .../platform/ThreadInformationTest.java | 231 ++ .../inspectit/util/ReflectionCacheTest.java | 74 + .../inspectit/util/StringConstraintTest.java | 184 ++ .../inspectit/util/ThreadLocalStackTest.java | 93 + .../novatec/inspectit/util/WeakListTest.java | 102 + CMR/.checkstyle | 14 + CMR/.classpath | 11 + CMR/.gitignore | 18 + CMR/.pmd | 7 + CMR/.project | 43 + CMR/.settings/org.apache.ivyde.eclipse.prefs | 2 + CMR/.settings/org.eclipse.jdt.core.prefs | 13 + CMR/.settings/org.eclipse.jdt.ui.prefs | 4 + CMR/build.properties | 54 + CMR/build.xml | 360 +++ CMR/config/default.xml | 318 +++ CMR/config/schema/configurationSchema.xsd | 265 ++ .../schema/configurationUpdateSchema.xsd | 92 + CMR/logging-config.xml | 146 + .../config/common/exclude-classes.cfg | 21 + .../config/common/sql-parameters.cfg | 89 + CMR/monitoring/config/common/sql.cfg | 52 + CMR/monitoring/config/inspectit-agent.cfg | 87 + CMR/monitoring/config/logging.properties | 17 + CMR/resources/ivy/ivy.xml | 68 + CMR/resources/launch/CMR with Agent.launch | 18 + CMR/resources/launch/CMR.launch | 19 + CMR/resources/testng/testng.xml | 54 + CMR/src/beanRefContext.xml | 16 + .../ClassLoadingInformationData.hbm.xml | 16 + .../CompilationInformationData.hbm.xml | 10 + CMR/src/hibernate/CpuInformationData.hbm.xml | 11 + CMR/src/hibernate/DefaultData.hbm.xml | 15 + CMR/src/hibernate/HttpTimerData.hbm.xml | 11 + .../hibernate/MemoryInformationData.hbm.xml | 28 + CMR/src/hibernate/MethodIdent.hbm.xml | 37 + .../hibernate/MethodIdentToSensorType.hbm.xml | 16 + CMR/src/hibernate/MethodSensorData.hbm.xml | 11 + .../hibernate/ParameterContentData.hbm.xml | 14 + CMR/src/hibernate/PlatformIdent.hbm.xml | 31 + .../hibernate/RuntimeInformationData.hbm.xml | 10 + CMR/src/hibernate/SensorTypeIdent.hbm.xml | 32 + CMR/src/hibernate/StorageLabel.hbm.xml | 39 + CMR/src/hibernate/StorageLabelType.hbm.xml | 46 + .../hibernate/SystemInformationData.hbm.xml | 30 + CMR/src/hibernate/SystemSensorData.hbm.xml | 5 + .../hibernate/ThreadInformationData.hbm.xml | 19 + CMR/src/hibernate/TimerData.hbm.xml | 19 + CMR/src/hibernate/VMArgumentData.hbm.xml | 12 + CMR/src/info/novatec/inspectit/cmr/CMR.java | 222 ++ .../cmr/cache/AbstractObjectSizes.java | 488 ++++ .../cmr/cache/AbstractObjectSizesIbm.java | 147 + .../novatec/inspectit/cmr/cache/IBuffer.java | 110 + .../inspectit/cmr/cache/IBufferElement.java | 122 + .../impl/AbstractBufferElementProcessor.java | 134 + .../impl/AnalyzeBufferElementProcessor.java | 56 + .../cmr/cache/impl/AtomicBuffer.java | 733 +++++ .../cmr/cache/impl/BufferAnalyzer.java | 47 + .../cmr/cache/impl/BufferElement.java | 130 + .../cmr/cache/impl/BufferEvictor.java | 47 + .../cmr/cache/impl/BufferIndexer.java | 48 + .../cmr/cache/impl/BufferProperties.java | 555 ++++ .../cmr/cache/impl/BufferWorker.java | 72 + .../impl/IndexBufferElementProcessor.java | 144 + .../cmr/cache/impl/ObjectSizes32Bits.java | 31 + .../cmr/cache/impl/ObjectSizes32BitsIbm.java | 29 + .../cmr/cache/impl/ObjectSizes64Bits.java | 53 + .../impl/ObjectSizes64BitsCompressedOops.java | 30 + .../ObjectSizes64BitsCompressedOopsIbm.java | 29 + .../cmr/cache/impl/ObjectSizes64BitsIbm.java | 28 + .../cmr/cache/impl/ObjectSizesFactory.java | 68 + .../inspectit/cmr/dao/DefaultDataDao.java | 114 + .../cmr/dao/ExceptionSensorDataDao.java | 135 + .../inspectit/cmr/dao/HttpTimerDataDao.java | 70 + .../inspectit/cmr/dao/InvocationDataDao.java | 135 + .../inspectit/cmr/dao/MethodIdentDao.java | 83 + .../cmr/dao/MethodIdentToSensorTypeDao.java | 42 + .../cmr/dao/MethodSensorTypeIdentDao.java | 70 + .../inspectit/cmr/dao/PlatformIdentDao.java | 97 + .../cmr/dao/PlatformSensorTypeIdentDao.java | 72 + .../novatec/inspectit/cmr/dao/SqlDataDao.java | 66 + .../inspectit/cmr/dao/StorageDataDao.java | 137 + .../inspectit/cmr/dao/TimerDataDao.java | 39 + .../cmr/dao/impl/AbstractBufferDataDao.java | 179 ++ .../BufferExceptionSensorDataDaoImpl.java | 109 + .../dao/impl/BufferHttpTimerDataDaoImpl.java | 65 + .../dao/impl/BufferInvocationDataDaoImpl.java | 100 + .../cmr/dao/impl/BufferSqlDataDaoImpl.java | 61 + .../cmr/dao/impl/BufferTimerDataDaoImpl.java | 44 + .../cmr/dao/impl/DefaultDataDaoImpl.java | 296 ++ .../cmr/dao/impl/MethodIdentDaoImpl.java | 121 + .../impl/MethodIdentToSensorTypeDaoImpl.java | 75 + .../impl/MethodSensorTypeIdentDaoImpl.java | 93 + .../cmr/dao/impl/PlatformIdentDaoImpl.java | 259 ++ .../impl/PlatformSensorTypeIdentDaoImpl.java | 93 + .../cmr/dao/impl/StorageDataDaoImpl.java | 285 ++ .../cmr/dao/impl/TimerDataAggregator.java | 310 +++ .../cmr/indexing/impl/RootBranchFactory.java | 119 + .../inspectit/cmr/jaxb/JAXBTransformator.java | 103 + .../cmr/jetty/FileUploadServlet.java | 111 + ...JettyWebApplicationContextInitializer.java | 99 + .../AbstractChainedCmrDataProcessor.java | 75 + .../processor/AbstractCmrDataProcessor.java | 67 + .../impl/BufferInserterCmrProcessor.java | 61 + .../impl/CacheIdGeneratorCmrProcessor.java | 41 + .../impl/ExceptionMessageCmrProcessor.java | 47 + .../processor/impl/IndexerCmrProcessor.java | 59 + .../impl/InvocationModifierCmrProcessor.java | 189 ++ .../processor/impl/RecorderCmrProcessor.java | 44 + .../impl/SessionInserterCmrProcessor.java | 56 + .../impl/SqlExclusiveTimeCmrProcessor.java | 38 + .../SystemInformationDataCmrProcessor.java | 42 + .../impl/TimerDataChartingCmrProcessor.java | 50 + .../cmr/property/PropertyManager.java | 350 +++ .../cmr/property/PropertyUpdateExecutor.java | 409 +++ .../cmr/rmi/KryoNetServerCreator.java | 113 + .../cmr/service/AgentStorageService.java | 231 ++ .../cmr/service/CmrManagementService.java | 207 ++ .../service/ExceptionDataAccessService.java | 141 + .../cmr/service/GlobalDataAccessService.java | 189 ++ .../service/HttpTimerDataAccessService.java | 88 + .../service/InvocationDataAccessService.java | 124 + .../cmr/service/RegistrationService.java | 323 +++ .../cmr/service/ServerStatusService.java | 87 + .../cmr/service/SqlDataAccessService.java | 83 + .../inspectit/cmr/service/StorageService.java | 591 ++++ .../cmr/service/TimerDataAccessService.java | 46 + .../cmr/service/rest/CmrRestfulService.java | 81 + .../service/rest/StorageRestfulService.java | 327 +++ .../cmr/service/rest/error/JsonError.java | 43 + .../cmr/spring/aop/ExceptionInterceptor.java | 44 + .../inspectit/cmr/spring/aop/MethodLog.java | 48 + .../cmr/spring/aop/MethodLogInterceptor.java | 176 ++ .../KryoHttpInvokerServiceExporter.java | 67 + .../exporter/KryoNetRmiServiceExporter.java | 92 + .../cmr/spring/exporter/RemotingExporter.java | 206 ++ .../CachingWriteDataProcessorProvider.java | 111 + .../cmr/storage/CmrStorageManager.java | 1221 +++++++++ .../cmr/storage/CmrStorageRecorder.java | 313 +++ .../cmr/storage/CmrStorageWriter.java | 163 ++ .../cmr/storage/CmrStorageWriterProvider.java | 16 + .../cmr/util/AgentStatusDataProvider.java | 94 + .../inspectit/cmr/util/CacheIdGenerator.java | 35 + .../novatec/inspectit/cmr/util/Converter.java | 33 + .../cmr/util/ExceptionEventType.java | 132 + .../inspectit/cmr/util/HealthStatus.java | 495 ++++ .../inspectit/cmr/util/HibernateUtil.java | 81 + .../inspectit/cmr/util/ListStringType.java | 171 ++ .../inspectit/cmr/util/MapStringType.java | 187 ++ .../cmr/util/PlatformIdentCache.java | 121 + .../inspectit/cmr/util/ShutdownService.java | 157 ++ CMR/src/spring/spring-context-beans.xml | 75 + CMR/src/spring/spring-context-database.xml | 77 + CMR/src/spring/spring-context-global.xml | 27 + CMR/src/spring/spring-context-jetty.xml | 327 +++ CMR/src/spring/spring-context-processors.xml | 51 + CMR/src/spring/spring-context-rest.xml | 32 + CMR/startup.bat | 9 + CMR/startup.sh | 9 + .../cmr/cache/impl/AtomicBufferTest.java | 437 +++ .../cmr/cache/impl/BufferPropertiesTest.java | 173 ++ .../cmr/cache/impl/MemoryCalculationTest.java | 659 +++++ .../cmr/dao/impl/PlatformIdentDaoTest.java | 40 + .../cmr/dao/impl/TimerDataAggregatorTest.java | 248 ++ .../processor/impl/CmrDataProcessorsTest.java | 527 ++++ .../cmr/property/PropertyIntegrationTest.java | 81 + .../cmr/property/PropertyManagerTest.java | 307 +++ .../property/PropertyUpdateExecutorTest.java | 221 ++ .../cmr/service/AgentStorageServiceTest.java | 95 + .../service/GlobalDataAccessServiceTest.java | 105 + .../cmr/service/RegistrationServiceTest.java | 610 +++++ .../rest/StorageRestfulServiceTest.java | 163 ++ .../cmr/storage/CmrStorageManagerTest.java | 444 +++ .../cmr/storage/CmrStorageRecorderTest.java | 181 ++ .../cmr/test/AbstractTestNGLogSupport.java | 49 + ...AbstractTransactionalTestNGLogSupport.java | 50 + .../cmr/util/AgentStatusDataProviderTest.java | 77 + .../cmr/util/PlatformIdentCacheTest.java | 61 + .../storage/StorageIntegrationTest.java | 484 ++++ .../impl/HibernateSerializerTest.java | 142 + .../schema/SchemaManagerTestProvider.java | 36 + .../spring/spring-context-storage-test.xml | 12 + Commons/.checkstyle | 13 + Commons/.classpath | 11 + Commons/.gitignore | 6 + Commons/.pmd | 7 + Commons/.project | 41 + .../.settings/org.apache.ivyde.eclipse.prefs | 2 + Commons/.settings/org.eclipse.jdt.core.prefs | 13 + Commons/.settings/org.eclipse.pde.core.prefs | 3 + Commons/.settings/org.eclipse.pde.prefs | 33 + Commons/META-INF/MANIFEST.MF | 54 + Commons/build.properties | 4 + Commons/resources/build.properties | 34 + Commons/resources/build.xml | 194 ++ .../installer-commons/Unix_shortcutSpec.xml | 25 + .../installer-commons/Win_shortcutSpec.xml | 39 + .../bin/langpacks/flags/eng-custom.gif | Bin 0 -> 1114 bytes .../bin/langpacks/installer/eng-custom.xml | 298 ++ .../installer/installer-commons/head-logo.png | Bin 0 -> 2691 bytes .../installer/installer-commons/install.xml | 188 ++ .../installer/installer-commons/license.html | 685 +++++ .../service/inspectITCMRw.exe | Bin 0 -> 104448 bytes .../service/template_installService.bat | 35 + .../service/template_uninstallService.bat | 10 + .../service/win32/prunsrv.exe | Bin 0 -> 80896 bytes .../service/win64/prunsrv.exe | Bin 0 -> 103936 bytes .../installer/installer-commons/side-logo.png | Bin 0 -> 56156 bytes .../welcome-information.html | 15 + Commons/resources/ivy/ivy.xml | 59 + .../shared/build/cmr-startup.properties | 12 + .../shared/build/common-targets.properties | 126 + .../resources/shared/build/common-targets.xml | 712 +++++ .../coding-templates/inspectit-cleanup.xml | 56 + .../inspectit-codetemplates.xml | 38 + .../coding-templates/inspectit-formatter.xml | 279 ++ .../shared/config/checkstyle/checkstyle.xsl | 179 ++ .../checkstyle/inspectit-checkstyle.xml | 103 + .../resources/shared/config/cpd/cpdhtml.xslt | 98 + .../shared/config/findbugs/fancy-hist.xsl | 1197 ++++++++ .../config/findbugs/findBugsExcludeFilter.xml | 136 + .../config/findbugs/findBugsIncludeFilter.xml | 5 + .../shared/config/ivy/ivysettings.xml | 13 + .../shared/config/pmd/pmd-report.xslt | 98 + .../resources/shared/config/pmd/pmd_rules.xml | 183 ++ .../resources/shared/config/pmd/sorttable.js | 184 ++ Commons/resources/testng/testng.xml | 22 + .../inspectit/cmr/cache/IObjectSizes.java | 279 ++ .../inspectit/cmr/model/MethodIdent.java | 387 +++ .../cmr/model/MethodIdentToSensorType.java | 209 ++ .../cmr/model/MethodSensorTypeIdent.java | 69 + .../model/MethodSensorTypeIdentHelper.java | 68 + .../inspectit/cmr/model/PlatformIdent.java | 256 ++ .../cmr/model/PlatformSensorTypeIdent.java | 17 + .../inspectit/cmr/model/SensorTypeIdent.java | 133 + .../cmr/property/spring/PropertyUpdate.java | 27 + .../cmr/service/IAgentStorageService.java | 30 + .../cmr/service/ICachedDataService.java | 42 + .../cmr/service/IRegistrationService.java | 132 + .../cmr/service/ServiceExporterType.java | 15 + .../cmr/service/ServiceInterface.java | 61 + .../service/exception/ServiceException.java | 53 + .../cmr/storage/util/IObjectCloner.java | 22 + .../inspectit/communication/DefaultData.java | 269 ++ .../communication/ExceptionEvent.java | 51 + .../communication/IAggregatedData.java | 27 + .../IIdsAwareAggregatedData.java | 27 + .../communication/MethodSensorData.java | 202 ++ .../inspectit/communication/Sizeable.java | 35 + .../communication/SystemSensorData.java | 39 + ...atedExceptionSensorDataComparatorEnum.java | 53 + .../comparator/DefaultDataComparatorEnum.java | 40 + .../ExceptionSensorDataComparatorEnum.java | 48 + .../HttpTimerDataComparatorEnum.java | 55 + .../comparator/IDataComparator.java | 31 + .../InvocationAwareDataComparatorEnum.java | 40 + .../InvocationSequenceDataComparatorEnum.java | 100 + .../MethodSensorDataComparatorEnum.java | 73 + .../comparator/ResultComparator.java | 161 ++ .../SqlStatementDataComparatorEnum.java | 91 + .../comparator/TimerDataComparatorEnum.java | 133 + .../data/AggregatedExceptionSensorData.java | 252 ++ .../data/AggregatedHttpTimerData.java | 134 + .../data/AggregatedSqlStatementData.java | 134 + .../data/AggregatedTimerData.java | 124 + .../data/ClassLoadingInformationData.java | 431 +++ .../data/CompilationInformationData.java | 209 ++ .../data/CpuInformationData.java | 272 ++ .../data/DatabaseAggregatedTimerData.java | 68 + .../data/ExceptionSensorData.java | 319 +++ .../communication/data/HttpTimerData.java | 365 +++ .../data/HttpTimerDataHelper.java | 73 + .../data/InvocationAwareData.java | 329 +++ .../data/InvocationSequenceData.java | 442 +++ .../data/InvocationSequenceDataHelper.java | 239 ++ .../data/MemoryInformationData.java | 828 ++++++ .../data/ParameterContentData.java | 254 ++ .../data/ParameterContentType.java | 52 + .../data/RuntimeInformationData.java | 209 ++ .../communication/data/SqlStatementData.java | 358 +++ .../data/SystemInformationData.java | 694 +++++ .../data/ThreadInformationData.java | 529 ++++ .../communication/data/TimerData.java | 693 +++++ .../communication/data/VmArgumentData.java | 152 + .../data/cmr/AgentStatusData.java | 171 ++ .../communication/data/cmr/CmrStatusData.java | 373 +++ .../communication/valueobject/TimerRawVO.java | 369 +++ .../inspectit/indexing/IIndexQuery.java | 167 ++ .../restriction/IIndexQueryRestriction.java | 41 + .../novatec/inspectit/kryonet/Client.java | 604 ++++ .../novatec/inspectit/kryonet/Connection.java | 406 +++ .../novatec/inspectit/kryonet/EndPoint.java | 61 + .../kryonet/ExtendedSerializationImpl.java | 162 ++ .../kryonet/IExtendedSerialization.java | 37 + .../novatec/inspectit/kryonet/Listener.java | 205 ++ .../inspectit/kryonet/Serialization.java | 34 + .../novatec/inspectit/kryonet/Server.java | 634 +++++ .../inspectit/kryonet/TcpConnection.java | 365 +++ .../inspectit/kryonet/UdpConnection.java | 151 + .../inspectit/kryonet/rmi/ObjectSpace.java | 738 +++++ .../novatec/inspectit/spring/logger/Log.java | 20 + .../spring/logger/LoggerPostProcessor.java | 44 + .../storage/nio/ByteBufferProvider.java | 281 ++ .../nio/bytebuffer/ByteBufferFactory.java | 82 + ...AbstractExtendedByteBufferInputStream.java | 344 +++ .../ExtendedByteBufferOutputStream.java | 179 ++ .../SocketExtendedByteBufferInputStream.java | 148 + .../storage/nio/stream/StreamProvider.java | 69 + .../HibernateAwareClassResolver.java | 85 + .../storage/serializer/IKryoProvider.java | 19 + .../storage/serializer/ISerializer.java | 62 + .../serializer/ISerializerProvider.java | 19 + .../serializer/SerializationException.java | 57 + .../impl/CustomCompatibleFieldSerializer.java | 213 ++ .../HibernateAwareCollectionSerializer.java | 53 + .../impl/HibernateAwareMapSerializer.java | 52 + .../impl/HibernateProxySerializer.java | 65 + .../impl/InvocationAwareDataSerializer.java | 74 + ...quenceCustomCompatibleFieldSerializer.java | 60 + .../serializer/impl/SerializationManager.java | 385 +++ .../impl/StackTraceElementSerializer.java | 61 + .../serializer/impl/TimestampSerializer.java | 41 + .../SerializationManagerProvider.java | 22 + .../serializer/schema/ClassSchema.java | 147 + .../serializer/schema/ClassSchemaManager.java | 251 ++ .../util/KryoSerializationPreferences.java | 22 + .../storage/serializer/util/KryoUtil.java | 39 + .../novatec/inspectit/util/ArrayUtil.java | 107 + .../inspectit/util/IHibernateUtil.java | 74 + .../inspectit/util/KryoNetNetwork.java | 49 + .../novatec/inspectit/util/ObjectUtils.java | 124 + .../novatec/inspectit/util/TimeFrame.java | 143 + .../inspectit/util/UnderlyingSystemInfo.java | 353 +++ .../FileBasedVersioningServiceImpl.java | 76 + .../versioning/IVersioningService.java | 22 + .../schema/AbstractCustomStorageLabelType.sch | 6 + .../src/schema/AbstractStorageLabelType.sch | 3 + .../schema/AggregatedExceptionSensorData.sch | 28 + .../src/schema/AggregatedHttpTimerData.sch | 43 + .../src/schema/AggregatedSqlStatementData.sch | 45 + Commons/src/schema/AggregatedTimerData.sch | 35 + Commons/src/schema/ArrayBasedStorageLeaf.sch | 8 + Commons/src/schema/BooleanStorageLabel.sch | 5 + .../schema/ClassLoadingInformationData.sch | 21 + .../src/schema/CompilationInformationData.sch | 15 + Commons/src/schema/CpuInformationData.sch | 16 + Commons/src/schema/DateStorageLabel.sch | 5 + Commons/src/schema/ExceptionSensorData.sch | 22 + Commons/src/schema/HttpTimerData.sch | 40 + Commons/src/schema/InvocationSequenceData.sch | 26 + Commons/src/schema/LeafWithNoDescriptors.sch | 4 + Commons/src/schema/LocalStorageData.sch | 9 + Commons/src/schema/MemoryInformationData.sch | 33 + Commons/src/schema/MethodIdent.sch | 14 + .../src/schema/MethodIdentToSensorType.sch | 7 + Commons/src/schema/MethodSensorTypeIdent.sch | 12 + Commons/src/schema/NumberStorageLabel.sch | 5 + Commons/src/schema/ObjectStorageLabel.sch | 5 + Commons/src/schema/ParameterContentData.sch | 10 + Commons/src/schema/PlatformIdent.sch | 10 + Commons/src/schema/RuntimeInformationData.sch | 15 + Commons/src/schema/SensorTypeIdent.sch | 7 + .../src/schema/SimpleStorageDescriptor.sch | 4 + Commons/src/schema/SqlStatementData.sch | 42 + Commons/src/schema/StorageBranch.sch | 6 + Commons/src/schema/StorageBranchIndexer.sch | 6 + Commons/src/schema/StorageData.sch | 9 + Commons/src/schema/StringStorageLabel.sch | 5 + Commons/src/schema/SystemInformationData.sch | 30 + Commons/src/schema/ThreadInformationData.sch | 24 + Commons/src/schema/TimeFrame.sch | 4 + Commons/src/schema/TimerData.sch | 30 + Commons/src/schema/TimestampIndexer.sch | 6 + Commons/src/schema/VmArgumentData.sch | 7 + Commons/src/schema/schemaList.txt | 39 + .../ObjectSizesPrimitiveTypesSizeTest.java | 234 ++ .../communication/EqualsVerifierTest.java | 71 + .../data/AggregatedDataTest.java | 92 + .../data/InvocationAwareDataTest.java | 131 + .../valueobject/TimerRawVOTest.java | 101 + ...cketExtendedByteBufferInputStreamTest.java | 101 + .../BackwardForwardCompatibilityTest.java | 217 ++ CommonsCS/.checkstyle | 13 + CommonsCS/.classpath | 12 + CommonsCS/.gitignore | 4 + CommonsCS/.pmd | 7 + CommonsCS/.project | 54 + .../.settings/org.apache.ivyde.eclipse.prefs | 2 + .../.settings/org.eclipse.jdt.core.prefs | 12 + .../.settings/org.eclipse.pde.core.prefs | 3 + CommonsCS/.settings/org.eclipse.pde.prefs | 33 + CommonsCS/META-INF/MANIFEST.MF | 68 + CommonsCS/build.properties | 4 + CommonsCS/resources/build.properties | 39 + CommonsCS/resources/build.xml | 195 ++ CommonsCS/resources/ivy/ivy.xml | 37 + CommonsCS/resources/testng/testng.xml | 68 + .../configuration/AbstractProperty.java | 132 + .../property/configuration/Configuration.java | 153 ++ .../configuration/GroupedProperty.java | 240 ++ .../configuration/PropertySection.java | 146 + .../configuration/SingleProperty.java | 423 +++ .../configuration/impl/BooleanProperty.java | 115 + .../configuration/impl/ByteProperty.java | 210 ++ .../configuration/impl/LongProperty.java | 116 + .../impl/PercentageProperty.java | 139 + .../configuration/impl/StringProperty.java | 115 + .../property/configuration/package-info.java | 7 + .../validation/PropertyValidation.java | 185 ++ .../PropertyValidationException.java | 60 + .../validation/ValidationError.java | 137 + .../validator/AbstractComparingValidator.java | 272 ++ .../AbstractSinglePropertyValidator.java | 69 + .../validator/IGroupedProperyValidator.java | 39 + .../validator/ISinglePropertyValidator.java | 39 + .../FullyQualifiedClassNameValidator.java | 34 + .../impl/GreaterOrEqualValidator.java | 67 + .../validator/impl/GreaterValidator.java | 67 + .../validator/impl/LessOrEqualValidator.java | 67 + .../validator/impl/LessValidator.java | 66 + .../validator/impl/NegativeValidator.java | 35 + .../validator/impl/NotEmptyValidator.java | 48 + .../validator/impl/PercentageValidator.java | 35 + .../validator/impl/PositiveValidator.java | 35 + .../update/AbstractPropertyUpdate.java | 125 + .../cmr/property/update/IPropertyUpdate.java | 33 + .../configuration/ConfigurationUpdate.java | 160 ++ .../update/impl/BooleanPropertyUpdate.java | 64 + .../update/impl/BytePropertyUpdate.java | 66 + .../update/impl/LongPropertyUpdate.java | 64 + .../update/impl/PercentagePropertyUpdate.java | 64 + .../impl/RestoreDefaultPropertyUpdate.java | 110 + .../update/impl/StringPropertyUpdate.java | 64 + .../cmr/property/update/package-info.java | 7 + .../cmr/service/ICmrManagementService.java | 75 + .../service/IExceptionDataAccessService.java | 137 + .../cmr/service/IGlobalDataAccessService.java | 130 + .../service/IHttpTimerDataAccessService.java | 93 + .../service/IInvocationDataAccessService.java | 133 + .../cmr/service/IServerStatusService.java | 87 + .../cmr/service/ISqlDataAccessService.java | 66 + .../cmr/service/IStorageService.java | 544 ++++ .../cmr/service/ITimerDataAccessService.java | 40 + .../cmr/service/cache/CachedDataService.java | 193 ++ .../communication/data/cmr/RecordingData.java | 188 ++ .../communication/data/cmr/WritingStatus.java | 73 + .../inspectit/indexing/AbstractBranch.java | 324 +++ .../inspectit/indexing/ITreeComponent.java | 73 + .../indexing/aggregation/Aggregators.java | 50 + .../indexing/aggregation/IAggregator.java | 46 + .../impl/AggregationPerformer.java | 125 + ...ClassLoadingInformationDataAggregator.java | 82 + .../impl/CpuInformationDataAggregator.java | 82 + .../impl/ExceptionDataAggregator.java | 169 ++ .../impl/HttpTimerDataAggregator.java | 149 + .../impl/MemoryInformationDataAggregator.java | 81 + .../impl/SqlStatementDataAggregator.java | 117 + .../impl/ThreadInformationDataAggregator.java | 82 + .../aggregation/impl/TimerDataAggregator.java | 78 + .../indexing/buffer/IBufferBranchIndexer.java | 29 + .../indexing/buffer/IBufferTreeComponent.java | 57 + .../indexing/buffer/impl/Branch.java | 124 + .../buffer/impl/BufferBranchIndexer.java | 129 + .../inspectit/indexing/buffer/impl/Leaf.java | 244 ++ .../inspectit/indexing/impl/IndexQuery.java | 342 +++ .../indexing/impl/IndexingException.java | 37 + .../AbstractSharedInstanceBranchIndexer.java | 56 + .../indexing/indexer/IBranchIndexer.java | 52 + .../impl/InvocationChildrenIndexer.java | 51 + .../indexer/impl/MethodIdentIndexer.java | 42 + .../indexer/impl/ObjectTypeIndexer.java | 49 + .../indexer/impl/PlatformIdentIndexer.java | 39 + .../indexer/impl/SensorTypeIdentIndexer.java | 39 + .../indexer/impl/SqlStringIndexer.java | 87 + .../indexer/impl/TimestampIndexer.java | 174 ++ .../query/factory/AbstractQueryFactory.java | 38 + .../impl/ExceptionSensorDataQueryFactory.java | 133 + .../impl/HttpTimerDataQueryFactory.java | 80 + .../InvocationSequenceDataQueryFactory.java | 79 + .../impl/SqlStatementDataQueryFactory.java | 65 + .../factory/impl/TimerDataQueryFactory.java | 54 + .../query/provider/IIndexQueryProvider.java | 21 + .../provider/impl/IndexQueryProvider.java | 29 + .../impl/StorageIndexQueryProvider.java | 27 + .../AbstractIndexQueryRestriction.java | 87 + .../IIndexQueryRestrictionProcessor.java | 25 + ...CachingIndexQueryRestrictionProcessor.java | 124 + .../impl/ComparableIndexQueryRestriction.java | 42 + .../impl/IndexQueryRestrictionFactory.java | 646 +++++ .../impl/ObjectIndexQueryRestriction.java | 39 + .../storage/AbstractStorageDescriptor.java | 27 + .../storage/IStorageBranchIndexer.java | 37 + .../indexing/storage/IStorageDescriptor.java | 46 + .../storage/IStorageTreeComponent.java | 19 + .../storage/impl/ArrayBasedStorageLeaf.java | 425 +++ .../storage/impl/CombinedStorageBranch.java | 170 ++ .../storage/impl/LeafWithNoDescriptors.java | 238 ++ .../storage/impl/SimpleStorageDescriptor.java | 160 ++ .../indexing/storage/impl/StorageBranch.java | 117 + .../storage/impl/StorageBranchIndexer.java | 279 ++ .../storage/impl/StorageDescriptor.java | 215 ++ .../storage/impl/StorageIndexQuery.java | 185 ++ .../impl/StorageRootBranchFactory.java | 53 + .../storage/AbstractStorageData.java | 186 ++ .../inspectit/storage/IStorageData.java | 65 + .../novatec/inspectit/storage/IWriter.java | 42 + .../inspectit/storage/LocalStorageData.java | 109 + .../inspectit/storage/StorageData.java | 236 ++ .../inspectit/storage/StorageException.java | 51 + .../inspectit/storage/StorageFileType.java | 106 + .../storage/StorageIndexingTreeHandler.java | 432 +++ .../inspectit/storage/StorageManager.java | 764 ++++++ .../inspectit/storage/StorageWriter.java | 833 ++++++ .../storage/label/AbstractStorageLabel.java | 90 + .../storage/label/BooleanStorageLabel.java | 170 ++ .../storage/label/DateStorageLabel.java | 177 ++ .../storage/label/NumberStorageLabel.java | 179 ++ .../storage/label/ObjectStorageLabel.java | 149 + .../storage/label/StringStorageLabel.java | 167 ++ .../AbstractLabelManagementAction.java | 99 + .../impl/AddLabelManagementAction.java | 65 + .../impl/RemoveLabelManagementAction.java | 101 + .../type/AbstractCustomStorageLabelType.java | 170 ++ .../label/type/AbstractStorageLabelType.java | 133 + .../label/type/impl/AssigneeLabelType.java | 50 + .../type/impl/CreationDateLabelType.java | 60 + .../type/impl/CustomBooleanLabelType.java | 34 + .../label/type/impl/CustomDateLabelType.java | 28 + .../type/impl/CustomNumberLabelType.java | 26 + .../type/impl/CustomStringLabelType.java | 26 + .../type/impl/DataTimeFrameLabelType.java | 67 + .../label/type/impl/ExploredByLabelType.java | 58 + .../label/type/impl/RatingLabelType.java | 50 + .../label/type/impl/StatusLabelType.java | 50 + .../label/type/impl/UseCaseLabelType.java | 50 + .../storage/nio/AbstractChannelManager.java | 212 ++ .../storage/nio/CustomAsyncChannel.java | 270 ++ .../storage/nio/WriteReadAttachment.java | 157 ++ .../nio/WriteReadCompletionRunnable.java | 148 + .../nio/read/ReadingChannelManager.java | 100 + .../nio/read/ReadingCompletionHandler.java | 78 + .../stream/ExtendedByteBufferInputStream.java | 329 +++ .../nio/stream/InputStreamProvider.java | 37 + .../nio/write/WritingChannelManager.java | 144 + .../nio/write/WritingCompletionHandler.java | 66 + .../AbstractChainedDataProcessor.java | 128 + .../processor/AbstractDataProcessor.java | 107 + .../AbstractExtractorDataProcessor.java | 37 + .../impl/AgentFilterDataProcessor.java | 65 + .../impl/DataAggregatorProcessor.java | 284 ++ .../processor/impl/DataSaverProcessor.java | 95 + .../impl/InvocationClonerDataProcessor.java | 47 + .../InvocationExtractorDataProcessor.java | 90 + .../impl/TimeFrameDataProcessor.java | 118 + .../write/AbstractWriteDataProcessor.java | 91 + .../write/impl/QueryCachingDataProcessor.java | 103 + .../recording/RecordingProperties.java | 183 ++ .../storage/recording/RecordingState.java | 24 + .../SerializationManagerPostProcessor.java | 245 ++ .../impl/ServerStatusSerializer.java | 51 + .../storage/util/CopyMoveFileVisitor.java | 98 + .../storage/util/DeleteFileVisitor.java | 40 + .../storage/util/ExecutorServiceFactory.java | 125 + .../storage/util/RangeDescriptor.java | 92 + .../util/StorageDeleteFileVisitor.java | 77 + .../util/StorageIndexTreeProvider.java | 21 + .../inspectit/storage/util/StorageUtil.java | 28 + .../cmr/property/XmlTransformationTest.java | 324 +++ .../configuration/ConfigurationTest.java | 56 + .../configuration/GroupedPropertyTest.java | 176 ++ .../configuration/SinglePropertyTest.java | 193 ++ .../validator/ValidatorsTest.java | 239 ++ .../update/ConfigurationUpdateTest.java | 71 + .../service/cache/CachedDataServiceTest.java | 92 + .../impl/HttpDataAggregatorTest.java | 166 ++ .../indexing/buffer/impl/BranchTest.java | 112 + .../buffer/impl/BufferBranchIndexerTest.java | 97 + .../indexing/impl/BufferIndexingTest.java | 366 +++ .../indexing/impl/StorageIndexingTest.java | 433 +++ .../indexer/impl/BranchIndexersTest.java | 255 ++ .../IndexQueryRestrictionProcessorTest.java | 246 ++ .../impl/ArrayBasedStorageLeafTest.java | 140 + .../impl/StorageBranchIndexerTest.java | 146 + .../inspectit/storage/StorageDataTest.java | 93 + .../StorageIndexingTreeHandlerTest.java | 151 + .../inspectit/storage/StorageWriterTest.java | 301 ++ .../impl/LabelManagementActionsTest.java | 130 + .../storage/nio/ByteBufferProviderTest.java | 167 ++ .../storage/nio/ChannelManagersTest.java | 234 ++ .../storage/nio/CompletionHandlersTest.java | 169 ++ .../ExtendedByteBufferInputStreamTest.java | 142 + .../ExtendedByteBufferOutputStreamTest.java | 128 + .../impl/StorageDataProcessorsTest.java | 300 ++ .../serializer/impl/SerializerTest.java | 386 +++ .../schema/SchemaManagerTestProvider.java | 36 + .../storage/serializer/schema/SchemaTest.java | 103 + .../storage/util/FileVisitorsTest.java | 128 + LICENSE.txt | 536 ++++ LICENSEEXCEPTIONS.txt | 37 + THIRDPARTYLICENSE.txt | 2000 ++++++++++++++ inspectIT/.checkstyle | 14 + inspectIT/.classpath | 10 + inspectIT/.gitignore | 13 + inspectIT/.pmd | 7 + inspectIT/.project | 54 + .../.settings/org.apache.ivyde.eclipse.prefs | 2 + .../.settings/org.eclipse.jdt.core.prefs | 12 + inspectIT/.settings/org.eclipse.jdt.ui.prefs | 3 + inspectIT/.settings/org.eclipse.pde.prefs | 33 + inspectIT/META-INF/MANIFEST.MF | 53 + .../spring/spring-context-model-main.xml | 262 ++ .../spring/spring-context-model-osgi.xml | 20 + .../spring/spring-context-model-other.xml | 27 + .../spring/spring-context-model-storage.xml | 105 + inspectIT/about.png | Bin 0 -> 65572 bytes inspectIT/build.properties | 13 + inspectIT/content/introContent.xml | 4 + inspectIT/content/static/documentation.html | 63 + inspectIT/content/static/fonts/Abel.woff | Bin 0 -> 15768 bytes .../content/static/graphics/background.jpg | Bin 0 -> 110065 bytes .../static/graphics/backgroundDown.png | Bin 0 -> 252 bytes .../static/graphics/backgroundMenu.png | Bin 0 -> 302 bytes .../static/graphics/documentationActive.png | Bin 0 -> 16534 bytes .../static/graphics/documentationInactive.png | Bin 0 -> 17077 bytes .../static/graphics/inspectit_logo.png | Bin 0 -> 12428 bytes .../static/graphics/inspectit_slogan.png | Bin 0 -> 1997 bytes .../static/graphics/newFeaturesActive.png | Bin 0 -> 17726 bytes .../static/graphics/newFeaturesInactive.png | Bin 0 -> 18193 bytes .../content/static/graphics/novatec_logo.png | Bin 0 -> 3811 bytes .../produktkarton_inspectit_small.png | Bin 0 -> 23418 bytes .../content/static/graphics/welcomeActive.png | Bin 0 -> 15593 bytes .../static/graphics/welcomeInactive.png | Bin 0 -> 15686 bytes .../content/static/icons/communityIcon.png | Bin 0 -> 2092 bytes .../content/static/icons/contactUsIcon.png | Bin 0 -> 1354 bytes .../static/icons/documentationIcon.png | Bin 0 -> 1951 bytes inspectIT/content/static/icons/export.png | Bin 0 -> 1191 bytes inspectIT/content/static/icons/homeIcon.png | Bin 0 -> 1822 bytes .../content/static/icons/newFeaturesIcon.png | Bin 0 -> 2950 bytes .../content/static/icons/supportIcon.png | Bin 0 -> 1602 bytes inspectIT/content/static/main.css | 217 ++ inspectIT/content/static/newFeatures.html | 64 + inspectIT/content/static/welcome.html | 83 + inspectIT/icons/eclipse/LICENSE-NOTICE.txt | 237 ++ inspectIT/icons/eclipse/add_obj.gif | Bin 0 -> 318 bytes inspectIT/icons/eclipse/agent.gif | Bin 0 -> 630 bytes inspectIT/icons/eclipse/agent_active.gif | Bin 0 -> 1074 bytes inspectIT/icons/eclipse/agent_na.gif | Bin 0 -> 1085 bytes inspectIT/icons/eclipse/agent_not_sending.gif | Bin 0 -> 1092 bytes inspectIT/icons/eclipse/alert_obj.gif | Bin 0 -> 326 bytes inspectIT/icons/eclipse/all_instances.gif | Bin 0 -> 97 bytes inspectIT/icons/eclipse/buffer_clear.gif | Bin 0 -> 392 bytes inspectIT/icons/eclipse/buffer_copy.gif | Bin 0 -> 601 bytes inspectIT/icons/eclipse/build.gif | Bin 0 -> 349 bytes inspectIT/icons/eclipse/business.gif | Bin 0 -> 379 bytes inspectIT/icons/eclipse/calendar.gif | Bin 0 -> 420 bytes inspectIT/icons/eclipse/call_hierarchy.gif | Bin 0 -> 209 bytes inspectIT/icons/eclipse/catalog.gif | Bin 0 -> 650 bytes inspectIT/icons/eclipse/class_obj.gif | Bin 0 -> 586 bytes inspectIT/icons/eclipse/collapseall.gif | Bin 0 -> 157 bytes inspectIT/icons/eclipse/complete_status.gif | Bin 0 -> 76 bytes inspectIT/icons/eclipse/dates.gif | Bin 0 -> 181 bytes inspectIT/icons/eclipse/debugt_obj.gif | Bin 0 -> 243 bytes inspectIT/icons/eclipse/debugtt_obj.gif | Bin 0 -> 159 bytes inspectIT/icons/eclipse/defaultview_misc.gif | Bin 0 -> 351 bytes inspectIT/icons/eclipse/delete_obj.gif | Bin 0 -> 351 bytes inspectIT/icons/eclipse/disabled_co.gif | Bin 0 -> 148 bytes inspectIT/icons/eclipse/discovery.gif | Bin 0 -> 362 bytes inspectIT/icons/eclipse/edit.gif | Bin 0 -> 651 bytes inspectIT/icons/eclipse/exceptiontracer.gif | Bin 0 -> 601 bytes inspectIT/icons/eclipse/exceptiontree.gif | Bin 0 -> 101 bytes inspectIT/icons/eclipse/export.gif | Bin 0 -> 329 bytes inspectIT/icons/eclipse/filter_ps.gif | Bin 0 -> 211 bytes inspectIT/icons/eclipse/flag.gif | Bin 0 -> 321 bytes inspectIT/icons/eclipse/font.gif | Bin 0 -> 112 bytes inspectIT/icons/eclipse/gel_sc_obj.gif | Bin 0 -> 380 bytes inspectIT/icons/eclipse/graph_bar.gif | Bin 0 -> 401 bytes inspectIT/icons/eclipse/graph_pie.gif | Bin 0 -> 679 bytes inspectIT/icons/eclipse/help.gif | Bin 0 -> 259 bytes inspectIT/icons/eclipse/home_nav.gif | Bin 0 -> 582 bytes inspectIT/icons/eclipse/ihigh_obj.gif | Bin 0 -> 202 bytes inspectIT/icons/eclipse/import.gif | Bin 0 -> 327 bytes inspectIT/icons/eclipse/info_obj.gif | Bin 0 -> 121 bytes inspectIT/icons/eclipse/insp_sbook.gif | Bin 0 -> 546 bytes inspectIT/icons/eclipse/label.gif | Bin 0 -> 633 bytes inspectIT/icons/eclipse/label_add.gif | Bin 0 -> 644 bytes inspectIT/icons/eclipse/label_delete.gif | Bin 0 -> 657 bytes .../icons/eclipse/locate_in_hierarchy.gif | Bin 0 -> 540 bytes inspectIT/icons/eclipse/message.gif | Bin 0 -> 598 bytes inspectIT/icons/eclipse/methdef_obj.gif | Bin 0 -> 176 bytes inspectIT/icons/eclipse/method.gif | Bin 0 -> 599 bytes inspectIT/icons/eclipse/method_time.gif | Bin 0 -> 629 bytes inspectIT/icons/eclipse/methpri_obj.gif | Bin 0 -> 183 bytes inspectIT/icons/eclipse/methpro_obj.gif | Bin 0 -> 181 bytes inspectIT/icons/eclipse/methpub_obj.gif | Bin 0 -> 193 bytes inspectIT/icons/eclipse/next_nav.gif | Bin 0 -> 332 bytes inspectIT/icons/eclipse/number.gif | Bin 0 -> 600 bytes inspectIT/icons/eclipse/options.gif | Bin 0 -> 624 bytes inspectIT/icons/eclipse/over_co.gif | Bin 0 -> 79 bytes inspectIT/icons/eclipse/overlay_error.gif | Bin 0 -> 292 bytes inspectIT/icons/eclipse/overlay_priority.gif | Bin 0 -> 123 bytes inspectIT/icons/eclipse/package_obj.gif | Bin 0 -> 227 bytes inspectIT/icons/eclipse/preferences.gif | Bin 0 -> 390 bytes inspectIT/icons/eclipse/prev_nav.gif | Bin 0 -> 323 bytes inspectIT/icons/eclipse/prj_obj.gif | Bin 0 -> 351 bytes inspectIT/icons/eclipse/properties.gif | Bin 0 -> 578 bytes inspectIT/icons/eclipse/pview.gif | Bin 0 -> 219 bytes inspectIT/icons/eclipse/read_obj.gif | Bin 0 -> 157 bytes inspectIT/icons/eclipse/record.gif | Bin 0 -> 269 bytes inspectIT/icons/eclipse/record_gray.gif | Bin 0 -> 970 bytes inspectIT/icons/eclipse/record_schedule.gif | Bin 0 -> 377 bytes inspectIT/icons/eclipse/record_term.gif | Bin 0 -> 353 bytes inspectIT/icons/eclipse/refresh.gif | Bin 0 -> 327 bytes inspectIT/icons/eclipse/remove_co.gif | Bin 0 -> 163 bytes inspectIT/icons/eclipse/remove_correction.gif | Bin 0 -> 185 bytes inspectIT/icons/eclipse/remove_exc.gif | Bin 0 -> 159 bytes inspectIT/icons/eclipse/server_instance.gif | Bin 0 -> 627 bytes inspectIT/icons/eclipse/showcat_co.gif | Bin 0 -> 218 bytes inspectIT/icons/eclipse/stacktrace.gif | Bin 0 -> 104 bytes inspectIT/icons/eclipse/start_task.gif | Bin 0 -> 318 bytes inspectIT/icons/eclipse/storage.gif | Bin 0 -> 607 bytes inspectIT/icons/eclipse/storage_available.gif | Bin 0 -> 621 bytes inspectIT/icons/eclipse/storage_download.gif | Bin 0 -> 622 bytes inspectIT/icons/eclipse/storage_finalize.gif | Bin 0 -> 658 bytes inspectIT/icons/eclipse/storage_na.gif | Bin 0 -> 628 bytes inspectIT/icons/eclipse/storage_overlay.gif | Bin 0 -> 556 bytes inspectIT/icons/eclipse/storage_readable.gif | Bin 0 -> 417 bytes inspectIT/icons/eclipse/storage_recording.gif | Bin 0 -> 590 bytes inspectIT/icons/eclipse/storage_upload.gif | Bin 0 -> 616 bytes inspectIT/icons/eclipse/storage_writable.gif | Bin 0 -> 561 bytes inspectIT/icons/eclipse/term_restart.gif | Bin 0 -> 351 bytes inspectIT/icons/eclipse/terminate_co.gif | Bin 0 -> 215 bytes inspectIT/icons/eclipse/time.gif | Bin 0 -> 686 bytes inspectIT/icons/eclipse/time_delta.gif | Bin 0 -> 655 bytes inspectIT/icons/eclipse/timeframe.gif | Bin 0 -> 353 bytes inspectIT/icons/eclipse/transform.gif | Bin 0 -> 230 bytes inspectIT/icons/eclipse/trash.gif | Bin 0 -> 590 bytes inspectIT/icons/eclipse/url.gif | Bin 0 -> 556 bytes inspectIT/icons/eclipse/user.gif | Bin 0 -> 361 bytes inspectIT/icons/eclipse/warning_obj.gif | Bin 0 -> 324 bytes inspectIT/icons/eclipse/wizban/add_wiz.png | Bin 0 -> 2068 bytes .../icons/eclipse/wizban/download_wiz.png | Bin 0 -> 2570 bytes inspectIT/icons/eclipse/wizban/edit_wiz.png | Bin 0 -> 3309 bytes inspectIT/icons/eclipse/wizban/export_wiz.png | Bin 0 -> 6675 bytes inspectIT/icons/eclipse/wizban/import_wiz.png | Bin 0 -> 6650 bytes inspectIT/icons/eclipse/wizban/label_wiz.png | Bin 0 -> 4220 bytes inspectIT/icons/eclipse/wizban/record_wiz.png | Bin 0 -> 3010 bytes inspectIT/icons/eclipse/wizban/server_wiz.png | Bin 0 -> 3164 bytes .../icons/eclipse/wizban/storage_wiz.png | Bin 0 -> 2911 bytes inspectIT/icons/eclipse/wizban/upload_wiz.png | Bin 0 -> 2576 bytes inspectIT/icons/eclipse/workset.gif | Bin 0 -> 582 bytes inspectIT/icons/fugue/LICENSE-NOTICE.txt | 24 + inspectIT/icons/fugue/arrow-switch.png | Bin 0 -> 877 bytes inspectIT/icons/fugue/blue-document-tree.png | Bin 0 -> 567 bytes inspectIT/icons/fugue/database-sql.png | Bin 0 -> 748 bytes inspectIT/icons/fugue/document-tree.png | Bin 0 -> 575 bytes inspectIT/icons/fugue/memory.png | Bin 0 -> 349 bytes inspectIT/icons/fugue/processor.png | Bin 0 -> 635 bytes inspectIT/icons/fugue/resource-monitor.png | Bin 0 -> 1478 bytes inspectIT/icons/fugue/system-monitor.png | Bin 0 -> 547 bytes inspectIT/icons/selfmade/128x128.png | Bin 0 -> 6669 bytes inspectIT/icons/selfmade/16x16.png | Bin 0 -> 607 bytes inspectIT/icons/selfmade/16x16_32bit.bmp | Bin 0 -> 1080 bytes inspectIT/icons/selfmade/32x32.png | Bin 0 -> 1371 bytes inspectIT/icons/selfmade/32x32_32bit.bmp | Bin 0 -> 4152 bytes inspectIT/icons/selfmade/48x48.png | Bin 0 -> 2133 bytes inspectIT/icons/selfmade/48x48_32bit.bmp | Bin 0 -> 9272 bytes inspectIT/icons/selfmade/64x64.png | Bin 0 -> 3085 bytes inspectIT/icons/selfmade/field.png | Bin 0 -> 890 bytes inspectIT/icons/selfmade/inspectIT.icns | Bin 0 -> 37442 bytes inspectIT/icons/selfmade/inspectIT.ico | Bin 0 -> 24238 bytes inspectIT/icons/selfmade/inspectIT.xpm | 488 ++++ inspectIT/icons/selfmade/parameter.png | Bin 0 -> 900 bytes inspectIT/icons/selfmade/return.png | Bin 0 -> 833 bytes inspectIT/icons/selfmade/server_add.png | Bin 0 -> 1645 bytes inspectIT/icons/selfmade/server_offline.png | Bin 0 -> 2432 bytes .../icons/selfmade/server_offline_16x16.png | Bin 0 -> 962 bytes inspectIT/icons/selfmade/server_online.png | Bin 0 -> 2427 bytes .../icons/selfmade/server_online_16x16.png | Bin 0 -> 981 bytes inspectIT/icons/selfmade/server_refresh.png | Bin 0 -> 2584 bytes .../icons/selfmade/server_refresh_16x16.png | Bin 0 -> 1006 bytes inspectIT/icons/selfmade/server_remove.png | Bin 0 -> 1562 bytes inspectIT/inspectIT.product | 185 ++ inspectIT/inspectIT.target | 13 + inspectIT/logging-config.xml | 76 + inspectIT/plugin.properties | 1 + inspectIT/plugin.xml | 2440 +++++++++++++++++ inspectIT/plugin_customization.ini | 2 + inspectIT/release/build.properties | 338 +++ inspectIT/release/build.xml | 358 +++ inspectIT/release/resources/ivy/ivy.xml | 56 + inspectIT/release/resources/testng/testng.xml | 8 + inspectIT/splash.bmp | Bin 0 -> 747896 bytes .../novatec/inspectit/rcp/Application.java | 36 + .../rcp/ApplicationWorkbenchAdvisor.java | 47 + .../ApplicationWorkbenchWindowAdvisor.java | 38 + .../info/novatec/inspectit/rcp/InspectIT.java | 429 +++ .../inspectit/rcp/InspectITConstants.java | 36 + .../inspectit/rcp/InspectITImages.java | 159 ++ .../novatec/inspectit/rcp/Perspective.java | 40 + .../inspectit/rcp/action/MenuAction.java | 71 + .../composite/BreadcrumbTitleComposite.java | 350 +++ .../rcp/composite/StorageInfoComposite.java | 252 ++ .../rcp/details/DetailsCellContent.java | 218 ++ .../rcp/details/DetailsGenerationFactory.java | 79 + .../inspectit/rcp/details/DetailsTable.java | 322 +++ .../rcp/details/YesNoDetailsCellContent.java | 31 + .../details/generator/IDetailsGenerator.java | 44 + .../AggregatedDurationDetailsGenerator.java | 71 + .../impl/DurationDetailsGenerator.java | 62 + .../impl/ExceptionDetailsGenerator.java | 59 + .../impl/GeneralInfoDetailsGenerator.java | 47 + .../generator/impl/HttpDetailsGenerator.java | 90 + ...InvocationAffiliationDetailsGenerator.java | 55 + .../InvocationSequenceDetailsGenerator.java | 49 + .../impl/MethodInfoDetailsGenerator.java | 80 + .../ParameterContentDetailsGenerator.java | 104 + .../generator/impl/SqlDetailsGenerator.java | 47 + .../AddCmrRepositoryDefinitionDialog.java | 283 ++ .../inspectit/rcp/dialog/DetailsDialog.java | 360 +++ .../rcp/dialog/EditRepositoryDataDialog.java | 199 ++ .../inspectit/rcp/editor/AbstractSubView.java | 85 + .../inspectit/rcp/editor/ISubView.java | 146 + .../inspectit/rcp/editor/SubViewFactory.java | 216 ++ .../composite/AbstractCompositeSubView.java | 198 ++ .../composite/GridCompositeSubView.java | 168 ++ .../composite/SashCompositeSubView.java | 187 ++ .../composite/TabbedCompositeSubView.java | 211 ++ .../rcp/editor/graph/GraphSubView.java | 388 +++ .../rcp/editor/graph/PlotFactory.java | 70 + .../graph/plot/AbstractPlotController.java | 168 ++ .../plot/AbstractTimerDataPlotController.java | 267 ++ .../editor/graph/plot/DateAxisZoomNotify.java | 75 + .../plot/DefaultClassesPlotController.java | 334 +++ .../graph/plot/DefaultCpuPlotController.java | 323 +++ .../plot/DefaultMemoryPlotController.java | 443 +++ .../plot/DefaultThreadsPlotController.java | 407 +++ .../graph/plot/HttpTimerPlotController.java | 323 +++ .../rcp/editor/graph/plot/PlotController.java | 114 + .../graph/plot/TimerPlotController.java | 204 ++ .../graph/plot/YIntervalSeriesImproved.java | 64 + .../rcp/editor/graph/plot/ZoomListener.java | 19 + .../inputdefinition/EditorPropertiesData.java | 248 ++ .../inputdefinition/InputDefinition.java | 437 +++ ...mbinedInvocationsInputDefinitionExtra.java | 75 + .../ExceptionTypeInputDefinitionExtra.java | 71 + .../HttpChartingInputDefinitionExtra.java | 135 + .../extra/IInputDefinitionExtra.java | 11 + .../InputDefinitionExtrasMarkerFactory.java | 137 + ...avigationSteppingInputDefinitionExtra.java | 100 + .../SqlStatementInputDefinitionExtra.java | 71 + ...TimerDataChartingInputDefinitionExtra.java | 74 + .../preferences/FormPreferencePanel.java | 938 +++++++ .../editor/preferences/IPreferenceGroup.java | 11 + .../editor/preferences/IPreferencePanel.java | 106 + .../preferences/PreferenceControlFactory.java | 53 + .../preferences/PreferenceEventCallback.java | 78 + .../rcp/editor/preferences/PreferenceId.java | 188 ++ .../control/AbstractPreferenceControl.java | 37 + .../control/IPreferenceControl.java | 50 + .../control/SamplingRateControl.java | 225 ++ .../control/SamplingRateSelecterFactory.java | 36 + .../preferences/control/TimeLineControl.java | 235 ++ .../samplingrate/ISamplingRateMode.java | 36 + .../samplingrate/SamplingRateMode.java | 132 + .../rcp/editor/root/AbstractRootEditor.java | 702 +++++ .../rcp/editor/root/FormRootEditor.java | 128 + .../rcp/editor/root/IRootEditor.java | 71 + .../root/MultiSubViewSelectionProvider.java | 148 + .../rcp/editor/root/RootEditorInput.java | 119 + .../root/SubViewClassificationController.java | 38 + .../rcp/editor/search/ISearchExecutor.java | 41 + .../search/OpenedSearchControlCache.java | 72 + .../rcp/editor/search/SearchControl.java | 366 +++ .../search/criteria/SearchCriteria.java | 143 + .../editor/search/criteria/SearchResult.java | 171 ++ .../editor/search/factory/SearchFactory.java | 339 +++ .../search/helper/AbstractSearchHelper.java | 327 +++ .../DeferredTreeViewerSearchHelper.java | 73 + .../helper/TableViewerSearchHelper.java | 72 + .../table/RemoteTableViewerComparator.java | 85 + .../rcp/editor/table/TableSubView.java | 494 ++++ .../editor/table/TableViewerComparator.java | 44 + .../input/AbstractHttpInputController.java | 169 ++ .../input/AbstractTableInputController.java | 216 ++ ...AggregatedTimerSummaryInputController.java | 301 ++ .../ExceptionSensorInvocInputController.java | 584 ++++ ...oupedExceptionOverviewInputController.java | 510 ++++ .../input/HttpTimerDataInputController.java | 609 ++++ .../input/InvocOverviewInputController.java | 667 +++++ .../input/MethodInvocInputController.java | 659 +++++ .../input/MultiInvocDataInputController.java | 188 ++ ...avigationInvocOverviewInputController.java | 103 + ...SqlParameterAggregationInputControler.java | 436 +++ .../table/input/TableInputController.java | 203 ++ .../TaggedHttpTimerDataInputController.java | 401 +++ .../table/input/TimerDataInputController.java | 631 +++++ ...oupedExceptionOverviewInputController.java | 508 ++++ .../editor/testers/ActiveSubViewTester.java | 73 + .../testers/AddToSteppingObjectsTester.java | 47 + .../testers/MaximizeMinimizeTester.java | 31 + .../rcp/editor/testers/NavigationTester.java | 128 + .../testers/SubViewClassificationTester.java | 55 + .../rcp/editor/text/TextSubView.java | 182 ++ .../input/AbstractTextInputController.java | 149 + .../text/input/ClassesInputController.java | 118 + .../editor/text/input/CpuInputController.java | 120 + .../text/input/MemoryInputController.java | 225 ++ .../SqlInvocSummaryTextInputController.java | 425 +++ .../SqlStatementTextInputController.java | 202 ++ .../text/input/TextInputController.java | 61 + .../text/input/ThreadsInputController.java | 128 + ...tionOverviewStackTraceInputController.java | 58 + .../text/input/VmSummaryInputController.java | 698 +++++ .../tooltip/ColumnAwareToolTipSupport.java | 134 + .../tooltip/IColumnToolTipProvider.java | 35 + .../rcp/editor/tree/DeferredTreeViewer.java | 378 +++ .../rcp/editor/tree/SteppingTreeSubView.java | 585 ++++ .../rcp/editor/tree/TreeSubView.java | 475 ++++ .../rcp/editor/tree/TreeViewerComparator.java | 44 + .../input/AbstractTreeInputController.java | 248 ++ .../rcp/editor/tree/input/DeferredInvoc.java | 100 + .../input/DeferredInvocAdapterFactory.java | 38 + .../ExceptionMessagesTreeInputController.java | 420 +++ .../input/ExceptionTreeInputController.java | 422 +++ .../input/InvocDetailInputController.java | 743 +++++ .../editor/tree/input/SqlInputController.java | 656 +++++ .../tree/input/SqlInvocInputController.java | 657 +++++ .../SteppingInvocDetailInputController.java | 197 ++ .../input/SteppingTreeInputController.java | 88 + .../tree/input/TreeInputController.java | 189 ++ .../tree/util/DatabaseSqlTreeComparator.java | 49 + .../viewers/AbstractViewerComparator.java | 131 + .../CheckedDelegatingIndexLabelProvider.java | 106 + .../RawAggregatedResultComparator.java | 85 + .../viewers/StyledCellIndexLabelProvider.java | 163 ++ .../inspectit/rcp/filter/FilterComposite.java | 208 ++ .../rcp/form/CmrRepositoryPropertyForm.java | 684 +++++ .../rcp/form/StorageDataPropertyForm.java | 604 ++++ .../rcp/formatter/ColorFormatter.java | 109 + .../rcp/formatter/ImageFormatter.java | 289 ++ .../rcp/formatter/NumberFormatter.java | 338 +++ .../formatter/SensorTypeAvailabilityEnum.java | 78 + .../rcp/formatter/TextFormatter.java | 602 ++++ .../rcp/handlers/AbstractTemplateHandler.java | 127 + .../rcp/handlers/AddStorageLabelHandler.java | 61 + .../ClearRepositoryBufferHandler.java | 114 + .../handlers/CloseAndShowStorageHandler.java | 132 + .../rcp/handlers/CloseStorageHandler.java | 179 ++ .../rcp/handlers/CmrConfigurationHandler.java | 120 + .../handlers/CopyBufferToStorageHandler.java | 73 + .../handlers/CopyDataToStorageHandler.java | 83 + .../rcp/handlers/CopySqlQueryHandler.java | 38 + .../rcp/handlers/DeleteAgentHandler.java | 55 + .../handlers/DeleteLocalStorageHandler.java | 71 + .../rcp/handlers/DeleteStorageHandler.java | 139 + .../rcp/handlers/DetailsHandler.java | 68 + .../rcp/handlers/DownloadStorageHandler.java | 48 + .../handlers/EditCmrRepositoryHandler.java | 51 + .../rcp/handlers/EditStorageDataHandler.java | 64 + .../inspectit/rcp/handlers/EmptyHandler.java | 24 + .../handlers/ExportLocalStorageHandler.java | 40 + .../inspectit/rcp/handlers/FindHandler.java | 94 + .../handlers/HttpDisplayInChartHandler.java | 124 + .../InvocationsCombineDataHandler.java | 99 + .../inspectit/rcp/handlers/LocateHandler.java | 199 ++ .../handlers/MaximizeActiveViewHandler.java | 86 + .../NavigateToAggregatedSqlDataHandler.java | 93 + .../NavigateToAggregatedTimerDataHandler.java | 93 + .../NavigateToExceptionTypeHandler.java | 176 ++ .../NavigateToInvocationsHandler.java | 154 ++ .../rcp/handlers/NavigateToPlotting.java | 128 + ...avigateToStartMethodInvocationHandler.java | 87 + .../rcp/handlers/OpenUrlHandler.java | 137 + .../rcp/handlers/OpenViewHandler.java | 59 + .../rcp/handlers/RefreshEditorHandler.java | 33 + .../rcp/handlers/RefreshViewHandler.java | 32 + .../handlers/RemoveCmrRepositoryHandler.java | 50 + .../handlers/RemoveStorageLabelHandler.java | 87 + .../rcp/handlers/RenameEditorHandler.java | 50 + .../rcp/handlers/ShowHideColumnsHandler.java | 299 ++ .../handlers/ShowLabelsManagerHandler.java | 53 + .../rcp/handlers/ShowRepositoryHandler.java | 71 + .../rcp/handlers/ShutdownCmrHandler.java | 116 + .../rcp/handlers/StartRecordingHandler.java | 124 + .../rcp/handlers/StopRecordingHanlder.java | 104 + .../rcp/handlers/TableCopyHandler.java | 62 + .../rcp/handlers/TreeCollapseHandler.java | 52 + .../rcp/handlers/TreeCopyHandler.java | 62 + .../rcp/handlers/TreeExpandHandler.java | 53 + .../rcp/handlers/UploadStorageHandler.java | 41 + .../inspectit/rcp/log/LogListener.java | 43 + .../SearchDocumentationContributionItem.java | 142 + .../rcp/menu/ShowHideMenuManager.java | 182 ++ .../inspectit/rcp/model/AccessFlag.java | 42 + .../rcp/model/AgentFolderFactory.java | 189 ++ .../inspectit/rcp/model/AgentLeaf.java | 149 + .../inspectit/rcp/model/Component.java | 188 ++ .../inspectit/rcp/model/Composite.java | 58 + .../rcp/model/DeferredAgentsComposite.java | 150 + .../rcp/model/DeferredBrowserComposite.java | 198 ++ .../rcp/model/DeferredClassComposite.java | 199 ++ .../rcp/model/DeferredComposite.java | 90 + .../rcp/model/DeferredMethodComposite.java | 182 ++ .../rcp/model/DeferredPackageComposite.java | 199 ++ .../inspectit/rcp/model/DeferredType.java | 14 + .../rcp/model/ExceptionImageFactory.java | 90 + .../FilteredDeferredBrowserComposite.java | 84 + .../model/FilteredDeferredClassComposite.java | 150 + .../FilteredDeferredPackageComposite.java | 84 + .../rcp/model/GroupedLabelsComposite.java | 71 + .../novatec/inspectit/rcp/model/Leaf.java | 11 + .../novatec/inspectit/rcp/model/Modifier.java | 215 ++ .../rcp/model/ModifiersImageFactory.java | 68 + .../inspectit/rcp/model/SensorTypeEnum.java | 217 ++ .../inspectit/rcp/model/TreeModelManager.java | 725 +++++ .../rcp/model/storage/LocalStorageLeaf.java | 89 + .../storage/LocalStorageTreeModelManager.java | 105 + .../rcp/model/storage/StorageLeaf.java | 93 + .../storage/StorageTreeModelManager.java | 114 + .../InspectITPreferenceInitializer.java | 52 + .../rcp/preferences/PreferenceException.java | 40 + .../rcp/preferences/PreferencesConstants.java | 81 + .../rcp/preferences/PreferencesUtils.java | 391 +++ .../StringToPrimitiveTransformUtil.java | 120 + .../page/CmrRepositoryPreferencePage.java | 456 +++ ...lassCollectionPreferenceValueProvider.java | 45 + .../CmrRepositoryPreferenceValueProvider.java | 104 + .../CollectionPreferenceValueProvider.java | 101 + .../ColumnOrderPreferenceValueProvider.java | 77 + .../EnumSetPreferenceValueProvider.java | 75 + ...ctedRepositoryPreferenceValueProvider.java | 92 + .../MapPreferenceValueProvider.java | 79 + .../PreferenceValueProviderFactory.java | 132 + .../rcp/property/CmrConfigurationDialog.java | 282 ++ .../GroupedPropertyPreferencePage.java | 111 + .../rcp/property/IPropertyUpdateListener.java | 42 + .../rcp/property/PropertyPreferencePage.java | 378 +++ .../control/AbstractPropertyControl.java | 267 ++ .../control/impl/BooleanPropertyControl.java | 79 + .../control/impl/BytePropertyControl.java | 202 ++ .../control/impl/LongPropertyControl.java | 105 + .../impl/PercentagePropertyControl.java | 79 + .../control/impl/StringPropertyControl.java | 77 + .../ICmrRepositoryAndAgentProvider.java | 29 + .../rcp/provider/ICmrRepositoryProvider.java | 21 + .../provider/IInputDefinitionProvider.java | 17 + .../provider/ILocalStorageDataProvider.java | 17 + .../rcp/provider/IStorageDataProvider.java | 27 + .../CmrRepositoryChangeListener.java | 60 + .../repository/CmrRepositoryDefinition.java | 534 ++++ .../rcp/repository/CmrRepositoryManager.java | 304 ++ .../rcp/repository/RepositoryDefinition.java | 88 + .../StorageRepositoryDefinition.java | 264 ++ .../StorageRepositoryDefinitionProvider.java | 18 + .../RefreshEditorsCachedDataService.java | 63 + .../repository/service/cmr/CmrService.java | 129 + .../service/cmr/CmrServiceProvider.java | 202 ++ .../repository/service/cmr/ICmrService.java | 34 + .../KryoSimpleHttpInvokerRequestExecutor.java | 79 + .../CachingPlatformIdentInterceptor.java | 34 + .../service/cmr/proxy/InterceptorUtils.java | 91 + .../ServiceInterfaceDelegateInterceptor.java | 61 + .../cmr/proxy/ServiceMethodInterceptor.java | 76 + .../storage/AbstractStorageService.java | 467 ++++ .../StorageExceptionDataAccessService.java | 137 + .../StorageGlobalDataAccessService.java | 251 ++ .../StorageHttpTimerDataAccessService.java | 139 + .../StorageInvocationDataAccessService.java | 130 + .../storage/StorageServiceProvider.java | 178 ++ .../storage/StorageSqlDataAccessService.java | 85 + .../StorageTimerDataAccessService.java | 69 + .../inspectit/rcp/resource/CombinedIcon.java | 96 + .../statushandlers/CustomStatusHandler.java | 29 + .../rcp/storage/InspectITStorageManager.java | 1066 +++++++ .../rcp/storage/http/TransferDataMonitor.java | 293 ++ .../storage/http/TransferRateInputStream.java | 68 + .../http/TransferRateOutputStream.java | 53 + .../AbstractStorageLabelComposite.java | 52 + .../impl/BooleanStorageLabelComposite.java | 118 + .../impl/DateStorageLabelComposite.java | 119 + .../impl/NumberStorageLabelComposite.java | 138 + .../impl/StringStorageLabelComposite.java | 116 + .../label/edit/LabelValueEditingSupport.java | 404 +++ .../listener/StorageChangeListener.java | 38 + .../rcp/storage/util/DataRetriever.java | 791 ++++++ .../rcp/storage/util/DataUploader.java | 136 + .../util/DownloadHttpEntityWrapper.java | 47 + .../rcp/storage/util/MultipartEntityUtil.java | 63 + .../storage/util/UploadHttpEntityWrapper.java | 46 + .../inspectit/rcp/tester/AgentTester.java | 31 + .../rcp/tester/CanRefreshViewTester.java | 23 + .../rcp/tester/CmrOnlineStatusTester.java | 66 + .../rcp/tester/InputDefinitionTester.java | 36 + .../rcp/tester/StorageStateTester.java | 67 + .../inspectit/rcp/tester/TimerDataTester.java | 34 + .../rcp/util/AccessibleArrowImage.java | 162 ++ .../rcp/util/ElementOccurrenceCount.java | 96 + .../inspectit/rcp/util/ListenerList.java | 259 ++ .../rcp/util/OccurrenceFinderFactory.java | 579 ++++ .../rcp/util/SelectionProviderAdapter.java | 74 + .../rcp/util/data/DatabaseInfoHelper.java | 129 + .../data/RegExAggregatedHttpTimerData.java | 127 + .../inspectit/rcp/view/IRefreshableView.java | 22 + .../rcp/view/impl/DataExplorerView.java | 953 +++++++ .../rcp/view/impl/RepositoryManagerView.java | 961 +++++++ .../rcp/view/impl/StorageManagerView.java | 1452 ++++++++++ .../listener/TreeViewDoubleClickListener.java | 64 + .../StorageManagerTreeContentProvider.java | 75 + .../tree/StorageManagerTreeLabelProvider.java | 47 + .../rcp/view/tree/TreeContentProvider.java | 141 + .../rcp/view/tree/TreeLabelProvider.java | 89 + .../rcp/view/tree/TreeViewerComparator.java | 59 + .../util/SelectionProviderIntermediate.java | 127 + .../rcp/wizard/AddCmrRepositoryWizard.java | 83 + .../rcp/wizard/AddStorageLabelWizard.java | 99 + .../rcp/wizard/CopyBufferToStorageWizard.java | 283 ++ .../rcp/wizard/CopyDataToStorageWizard.java | 280 ++ .../rcp/wizard/CreateStorageWizard.java | 100 + .../rcp/wizard/DownloadStorageWizard.java | 206 ++ .../rcp/wizard/EditCmrRepositoryWizard.java | 104 + .../rcp/wizard/ExportStorageWizard.java | 207 ++ .../rcp/wizard/ImportStorageWizard.java | 204 ++ .../rcp/wizard/ManageLabelWizard.java | 93 + .../rcp/wizard/StartRecordingWizard.java | 311 +++ .../rcp/wizard/UploadStorageWizard.java | 110 + .../page/AddStorageLabelWizardPage.java | 501 ++++ .../rcp/wizard/page/DefineCmrWizardPage.java | 248 ++ .../page/DefineDataProcessorsWizardPage.java | 492 ++++ .../page/DefineNewStorageWizzardPage.java | 221 ++ .../wizard/page/DefineTimelineWizardPage.java | 411 +++ .../wizard/page/ExportStorageWizardPage.java | 133 + .../wizard/page/ImportStorageInfoPage.java | 254 ++ .../wizard/page/ImportStorageSelectPage.java | 199 ++ .../wizard/page/ManageLabelWizardPage.java | 1013 +++++++ .../page/NewOrExistsingStorageWizardPage.java | 57 + .../wizard/page/PreviewCmrDataWizardPage.java | 231 ++ .../wizard/page/SelectAgentsWizardPage.java | 284 ++ .../page/SelectExistingStorageWizardPage.java | 271 ++ .../page/StorageCompressionWizardPage.java | 64 + .../wizard/page/UploadStorageWizardPage.java | 214 ++ .../test/TimeframeDividerTest.java | 287 ++ .../search/factory/SearchFactoryTest.java | 302 ++ inspectITJMeter/.checkstyle | 14 + inspectITJMeter/.classpath | 14 + inspectITJMeter/.gitignore | 8 + inspectITJMeter/.pmd | 7 + inspectITJMeter/.project | 46 + .../.settings/org.apache.ivyde.eclipse.prefs | 2 + .../.settings/org.eclipse.jdt.core.prefs | 12 + inspectITJMeter/build.properties | 64 + inspectITJMeter/build.xml | 331 +++ inspectITJMeter/overwrite/config/logback.xml | 25 + .../info/novatec/inspectit/rcp/InspectIT.java | 102 + .../core/runtime/IProgressMonitor.java | 135 + .../org/eclipse/core/runtime/SubMonitor.java | 374 +++ inspectITJMeter/resources/ivy/ivy.xml | 45 + .../resources/testng/testng-localtest.xml | 9 + inspectITJMeter/resources/testng/testng.xml | 15 + inspectITJMeter/src/beanRefContext.xml | 17 + .../jmeter/InspectITAggregatedSQL.java | 44 + .../jmeter/InspectITAggregatedTimer.java | 43 + .../jmeter/InspectITExceptionResult.java | 47 + .../jmeter/InspectITGetConnectedAgents.java | 73 + .../jmeter/InspectITHttpAggregation.java | 22 + .../jmeter/InspectITHttpAggregationBase.java | 33 + .../InspectITHttpUsecaseAggregation.java | 22 + .../jmeter/InspectITInvocationDetails.java | 40 + .../jmeter/InspectITInvocationOverview.java | 59 + .../jmeter/InspectITSamplerBase.java | 262 ++ .../jmeter/data/AggregatedSQLResult.java | 8 + .../jmeter/data/AggregatedTimerResult.java | 8 + .../inspectit/jmeter/data/ConnectedAgent.java | 16 + .../jmeter/data/ConnectedAgents.java | 24 + .../jmeter/data/CountOnlyResult.java | 21 + .../jmeter/data/ExceptionResult.java | 9 + .../jmeter/data/HttpAggregatedResult.java | 8 + .../jmeter/data/HttpUsecaseResult.java | 8 + .../jmeter/data/InspectITResultMarker.java | 10 + .../jmeter/data/InvocationDetailResult.java | 26 + .../jmeter/data/InvocationOverviewResult.java | 15 + .../inspectit/jmeter/data/ResultBase.java | 20 + .../jmeter/util/ObjectConverter.java | 266 ++ .../inspectit/jmeter/util/ResultService.java | 98 + .../inspectit/jmeter/util/XStreamFactory.java | 61 + .../inspectit/jmeter/SamplerBaseTest.java | 295 ++ .../jmeter/localexecution/LocalRunner.java | 251 ++ .../jmeter/util/ResultServiceTest.java | 73 + 1423 files changed, 185259 insertions(+) create mode 100644 .gitignore create mode 100644 Agent/.checkstyle create mode 100644 Agent/.classpath create mode 100644 Agent/.gitignore create mode 100644 Agent/.pmd create mode 100644 Agent/.project create mode 100644 Agent/.settings/org.apache.ivyde.eclipse.prefs create mode 100644 Agent/.settings/org.eclipse.jdt.core.prefs create mode 100644 Agent/META-INF/MANIFEST.MF create mode 100644 Agent/build.properties create mode 100644 Agent/build.xml create mode 100644 Agent/config/common/ejb.cfg create mode 100644 Agent/config/common/exclude-classes.cfg create mode 100644 Agent/config/common/hibernate.cfg create mode 100644 Agent/config/common/http.cfg create mode 100644 Agent/config/common/jpa.cfg create mode 100644 Agent/config/common/jsf.cfg create mode 100644 Agent/config/common/jta.cfg create mode 100644 Agent/config/common/sql-parameters.cfg create mode 100644 Agent/config/common/sql.cfg create mode 100644 Agent/config/common/struts.cfg create mode 100644 Agent/config/common/webservice.cfg create mode 100644 Agent/config/inspectit-agent.cfg create mode 100644 Agent/config/logging.properties create mode 100644 Agent/logging-config.xml create mode 100644 Agent/resources/ivy/ivy.xml create mode 100644 Agent/resources/testng/testng.xml create mode 100644 Agent/src/config/bytebufferpool.properties create mode 100644 Agent/src/info/novatec/inspectit/agent/Agent.java create mode 100644 Agent/src/info/novatec/inspectit/agent/IAgent.java create mode 100644 Agent/src/info/novatec/inspectit/agent/SpringAgent.java create mode 100644 Agent/src/info/novatec/inspectit/agent/analyzer/IByteCodeAnalyzer.java create mode 100644 Agent/src/info/novatec/inspectit/agent/analyzer/IClassPoolAnalyzer.java create mode 100644 Agent/src/info/novatec/inspectit/agent/analyzer/IInheritanceAnalyzer.java create mode 100644 Agent/src/info/novatec/inspectit/agent/analyzer/IMatchPattern.java create mode 100644 Agent/src/info/novatec/inspectit/agent/analyzer/IMatcher.java create mode 100644 Agent/src/info/novatec/inspectit/agent/analyzer/impl/AbstractMatcher.java create mode 100644 Agent/src/info/novatec/inspectit/agent/analyzer/impl/AnnotationMatcher.java create mode 100644 Agent/src/info/novatec/inspectit/agent/analyzer/impl/ByteCodeAnalyzer.java create mode 100644 Agent/src/info/novatec/inspectit/agent/analyzer/impl/ClassPoolAnalyzer.java create mode 100644 Agent/src/info/novatec/inspectit/agent/analyzer/impl/DirectMatcher.java create mode 100644 Agent/src/info/novatec/inspectit/agent/analyzer/impl/IndirectMatcher.java create mode 100644 Agent/src/info/novatec/inspectit/agent/analyzer/impl/InheritanceAnalyzer.java create mode 100644 Agent/src/info/novatec/inspectit/agent/analyzer/impl/InterfaceMatcher.java create mode 100644 Agent/src/info/novatec/inspectit/agent/analyzer/impl/ModifierMatcher.java create mode 100644 Agent/src/info/novatec/inspectit/agent/analyzer/impl/SimpleMatchPattern.java create mode 100644 Agent/src/info/novatec/inspectit/agent/analyzer/impl/SuperclassMatcher.java create mode 100644 Agent/src/info/novatec/inspectit/agent/analyzer/impl/ThrowableMatcher.java create mode 100644 Agent/src/info/novatec/inspectit/agent/buffer/AbstractBufferStrategy.java create mode 100644 Agent/src/info/novatec/inspectit/agent/buffer/IBufferStrategy.java create mode 100644 Agent/src/info/novatec/inspectit/agent/buffer/impl/SimpleBufferStrategy.java create mode 100644 Agent/src/info/novatec/inspectit/agent/buffer/impl/SizeBufferStrategy.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/IConfigurationReader.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/IConfigurationStorage.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/IPropertyAccessor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/ParserException.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/PriorityEnum.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/PropertyAccessException.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/StorageException.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/impl/AbstractSensorConfig.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/impl/AbstractSensorTypeConfig.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/impl/ConfigurationStorage.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/impl/FileConfigurationReader.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/impl/MethodSensorTypeConfig.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/impl/PlatformSensorTypeConfig.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/impl/PropertyAccessor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/impl/RegisteredSensorConfig.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/impl/RepositoryConfig.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/impl/StrategyConfig.java create mode 100644 Agent/src/info/novatec/inspectit/agent/config/impl/UnregisteredSensorConfig.java create mode 100644 Agent/src/info/novatec/inspectit/agent/connection/AbstractRemoteMethodCall.java create mode 100644 Agent/src/info/novatec/inspectit/agent/connection/IConnection.java create mode 100644 Agent/src/info/novatec/inspectit/agent/connection/RegistrationException.java create mode 100644 Agent/src/info/novatec/inspectit/agent/connection/RetryException.java create mode 100644 Agent/src/info/novatec/inspectit/agent/connection/RetryStrategy.java create mode 100644 Agent/src/info/novatec/inspectit/agent/connection/ServerUnavailableException.java create mode 100644 Agent/src/info/novatec/inspectit/agent/connection/impl/AddDataObjects.java create mode 100644 Agent/src/info/novatec/inspectit/agent/connection/impl/AddSensorTypeToMethod.java create mode 100644 Agent/src/info/novatec/inspectit/agent/connection/impl/AdditiveWaitRetryStrategy.java create mode 100644 Agent/src/info/novatec/inspectit/agent/connection/impl/ExponentialBackoffRetryStrategy.java create mode 100644 Agent/src/info/novatec/inspectit/agent/connection/impl/KryoNetConnection.java create mode 100644 Agent/src/info/novatec/inspectit/agent/connection/impl/RegisterMethodIdent.java create mode 100644 Agent/src/info/novatec/inspectit/agent/connection/impl/RegisterMethodSensorType.java create mode 100644 Agent/src/info/novatec/inspectit/agent/connection/impl/RegisterPlatformSensorType.java create mode 100644 Agent/src/info/novatec/inspectit/agent/core/ICoreService.java create mode 100644 Agent/src/info/novatec/inspectit/agent/core/IIdManager.java create mode 100644 Agent/src/info/novatec/inspectit/agent/core/IObjectStorage.java create mode 100644 Agent/src/info/novatec/inspectit/agent/core/IdNotAvailableException.java create mode 100644 Agent/src/info/novatec/inspectit/agent/core/ListListener.java create mode 100644 Agent/src/info/novatec/inspectit/agent/core/impl/CoreService.java create mode 100644 Agent/src/info/novatec/inspectit/agent/core/impl/IdManager.java create mode 100644 Agent/src/info/novatec/inspectit/agent/hooking/IConstructorHook.java create mode 100644 Agent/src/info/novatec/inspectit/agent/hooking/IHook.java create mode 100644 Agent/src/info/novatec/inspectit/agent/hooking/IHookDispatcher.java create mode 100644 Agent/src/info/novatec/inspectit/agent/hooking/IHookDispatcherMapper.java create mode 100644 Agent/src/info/novatec/inspectit/agent/hooking/IHookInstrumenter.java create mode 100644 Agent/src/info/novatec/inspectit/agent/hooking/IMethodHook.java create mode 100644 Agent/src/info/novatec/inspectit/agent/hooking/impl/HookDispatcher.java create mode 100644 Agent/src/info/novatec/inspectit/agent/hooking/impl/HookException.java create mode 100644 Agent/src/info/novatec/inspectit/agent/hooking/impl/HookInstrumenter.java create mode 100644 Agent/src/info/novatec/inspectit/agent/javaagent/JavaAgent.java create mode 100644 Agent/src/info/novatec/inspectit/agent/jrebel/JRebelUtil.java create mode 100644 Agent/src/info/novatec/inspectit/agent/logback/LogInitializer.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sending/AbstractSendingStrategy.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sending/ISendingStrategy.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sending/impl/ListSizeStrategy.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sending/impl/TimeStrategy.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/ISensor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/exception/ExceptionSensor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/exception/ExceptionSensorHook.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/exception/IExceptionSensor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/exception/IExceptionSensorHook.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/exception/IdentityHashToDataObject.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/AbstractMethodSensor.java create mode 100755 Agent/src/info/novatec/inspectit/agent/sensor/method/IMethodSensor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/averagetimer/AverageTimerHook.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/averagetimer/AverageTimerSensor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/http/HttpHook.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/http/HttpRequestParameterExtractor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/http/HttpSensor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/http/StartEndMarker.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/invocationsequence/InvocationSequenceHook.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/invocationsequence/InvocationSequenceSensor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/invocationsequence/UnsupportedMethodException.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionHook.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataHook.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataSensor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataStorage.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionSensor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementHook.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementParameterHook.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementParameterSensor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementSensor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/StatementHook.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/StatementReflectionCache.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/StatementSensor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/StatementStorage.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/timer/AggregateTimerStorage.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/timer/ITimerStorage.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/timer/OptimizedTimerStorage.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/timer/PlainTimerStorage.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/timer/TimerHook.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/timer/TimerSensor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/method/timer/TimerStorageFactory.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/AbstractPlatformSensor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/ClassLoadingInformation.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/CompilationInformation.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/CpuInformation.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/IPlatformSensor.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/MemoryInformation.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/RuntimeInformation.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/SystemInformation.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/ThreadInformation.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/MemoryInfoProvider.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/OperatingSystemInfoProvider.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/PlatformSensorInfoProvider.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/RuntimeInfoProvider.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/ThreadInfoProvider.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultMemoryInfoProvider.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultOperatingSystemInfoProvider.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultPlatformSensorInfoProvider.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultRuntimeInfoProvider.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultThreadInfoProvider.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/factory/PlatformSensorInfoProviderFactory.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/ibm/IbmJava6OperatingSystemInfoProvider.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/ibm/IbmJava6PlatformSensorInfoProvider.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/sun/SunOperatingSystemInfoProvider.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/sun/SunPlatformSensorInfoProvider.java create mode 100644 Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/util/CpuUsageCalculator.java create mode 100644 Agent/src/info/novatec/inspectit/agent/spring/PrototypesProvider.java create mode 100644 Agent/src/info/novatec/inspectit/agent/spring/SpringConfiguration.java create mode 100644 Agent/src/info/novatec/inspectit/agent/spring/StrategyAndSensorConfiguration.java create mode 100644 Agent/src/info/novatec/inspectit/util/MessageFormatFormatter.java create mode 100644 Agent/src/info/novatec/inspectit/util/ReflectionCache.java create mode 100644 Agent/src/info/novatec/inspectit/util/StringConstraint.java create mode 100644 Agent/src/info/novatec/inspectit/util/ThreadLocalStack.java create mode 100644 Agent/src/info/novatec/inspectit/util/Timer.java create mode 100644 Agent/src/info/novatec/inspectit/util/WeakList.java create mode 100644 Agent/test/info/novatec/inspectit/agent/AbstractLogSupport.java create mode 100644 Agent/test/info/novatec/inspectit/agent/MockInit.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/AnnotationMatcherTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/DirectMatcherTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/IndirectMatcherTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/InterfaceMatcherTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/ModifierMatcherTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/SimpleMatchPatternTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/SuperclassMatcherTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/classes/AbstractSubTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/classes/AbstractTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/classes/EmptyClass.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/classes/ExceptionTestClass.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/classes/ExceptionalTestClass.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/classes/ISubTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/classes/ITest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/classes/ITestTwo.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/classes/MyTestError.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/classes/MyTestException.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/classes/TestClass.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/classes/TestClassLoader.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/impl/ByteCodeAnalyzerTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/impl/ClassPoolAnalyzerTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/analyzer/impl/InheritanceAnalyzerTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/buffer/impl/SimpleBufferStrategyTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/buffer/impl/SizeBufferStrategyTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/config/impl/ConfigurationFilesTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/config/impl/ConfigurationStorageTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/config/impl/FileConfigurationReaderTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/config/impl/PropertyAccessorTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/core/impl/CoreServiceTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/core/impl/IdManagerTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/hooking/impl/HookDispatcherTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/hooking/impl/HookInstrumenterTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sending/impl/ListSizeStrategyTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sending/impl/TimeStrategyTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/exception/ExceptionSensorHookTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/method/averagetimer/AverageTimerHookTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/method/http/HttpHookTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/method/http/HttpRequestParameterExtractorTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/method/invocationsequence/InvocationSequenceHookTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataExtractorTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataHookTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataStorageTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/JDBCUrlExtractorTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementHookTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementParameterHookTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/StatementHookTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/StatementSensorTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/StatementStorageTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/method/timer/TimerHookTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/platform/ClassLoadingInformationTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/platform/CompilationInformationTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/platform/CpuInformationTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/platform/CpuUsageCalculatorTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/platform/MemoryInformationTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/platform/RuntimeInformationTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/platform/SystemInformationTest.java create mode 100644 Agent/test/info/novatec/inspectit/agent/sensor/platform/ThreadInformationTest.java create mode 100644 Agent/test/info/novatec/inspectit/util/ReflectionCacheTest.java create mode 100644 Agent/test/info/novatec/inspectit/util/StringConstraintTest.java create mode 100644 Agent/test/info/novatec/inspectit/util/ThreadLocalStackTest.java create mode 100644 Agent/test/info/novatec/inspectit/util/WeakListTest.java create mode 100644 CMR/.checkstyle create mode 100644 CMR/.classpath create mode 100644 CMR/.gitignore create mode 100644 CMR/.pmd create mode 100644 CMR/.project create mode 100644 CMR/.settings/org.apache.ivyde.eclipse.prefs create mode 100644 CMR/.settings/org.eclipse.jdt.core.prefs create mode 100644 CMR/.settings/org.eclipse.jdt.ui.prefs create mode 100644 CMR/build.properties create mode 100644 CMR/build.xml create mode 100644 CMR/config/default.xml create mode 100644 CMR/config/schema/configurationSchema.xsd create mode 100644 CMR/config/schema/configurationUpdateSchema.xsd create mode 100644 CMR/logging-config.xml create mode 100644 CMR/monitoring/config/common/exclude-classes.cfg create mode 100644 CMR/monitoring/config/common/sql-parameters.cfg create mode 100644 CMR/monitoring/config/common/sql.cfg create mode 100644 CMR/monitoring/config/inspectit-agent.cfg create mode 100644 CMR/monitoring/config/logging.properties create mode 100644 CMR/resources/ivy/ivy.xml create mode 100644 CMR/resources/launch/CMR with Agent.launch create mode 100644 CMR/resources/launch/CMR.launch create mode 100644 CMR/resources/testng/testng.xml create mode 100644 CMR/src/beanRefContext.xml create mode 100644 CMR/src/hibernate/ClassLoadingInformationData.hbm.xml create mode 100644 CMR/src/hibernate/CompilationInformationData.hbm.xml create mode 100644 CMR/src/hibernate/CpuInformationData.hbm.xml create mode 100644 CMR/src/hibernate/DefaultData.hbm.xml create mode 100644 CMR/src/hibernate/HttpTimerData.hbm.xml create mode 100644 CMR/src/hibernate/MemoryInformationData.hbm.xml create mode 100644 CMR/src/hibernate/MethodIdent.hbm.xml create mode 100644 CMR/src/hibernate/MethodIdentToSensorType.hbm.xml create mode 100644 CMR/src/hibernate/MethodSensorData.hbm.xml create mode 100644 CMR/src/hibernate/ParameterContentData.hbm.xml create mode 100644 CMR/src/hibernate/PlatformIdent.hbm.xml create mode 100644 CMR/src/hibernate/RuntimeInformationData.hbm.xml create mode 100644 CMR/src/hibernate/SensorTypeIdent.hbm.xml create mode 100644 CMR/src/hibernate/StorageLabel.hbm.xml create mode 100644 CMR/src/hibernate/StorageLabelType.hbm.xml create mode 100644 CMR/src/hibernate/SystemInformationData.hbm.xml create mode 100644 CMR/src/hibernate/SystemSensorData.hbm.xml create mode 100644 CMR/src/hibernate/ThreadInformationData.hbm.xml create mode 100644 CMR/src/hibernate/TimerData.hbm.xml create mode 100644 CMR/src/hibernate/VMArgumentData.hbm.xml create mode 100644 CMR/src/info/novatec/inspectit/cmr/CMR.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/AbstractObjectSizes.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/AbstractObjectSizesIbm.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/IBuffer.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/IBufferElement.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/impl/AbstractBufferElementProcessor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/impl/AnalyzeBufferElementProcessor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/impl/AtomicBuffer.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferAnalyzer.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferElement.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferEvictor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferIndexer.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferProperties.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferWorker.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/impl/IndexBufferElementProcessor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes32Bits.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes32BitsIbm.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes64Bits.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes64BitsCompressedOops.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes64BitsCompressedOopsIbm.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes64BitsIbm.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizesFactory.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/DefaultDataDao.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/ExceptionSensorDataDao.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/HttpTimerDataDao.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/InvocationDataDao.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/MethodIdentDao.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/MethodIdentToSensorTypeDao.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/MethodSensorTypeIdentDao.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/PlatformIdentDao.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/PlatformSensorTypeIdentDao.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/SqlDataDao.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/StorageDataDao.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/TimerDataDao.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/impl/AbstractBufferDataDao.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferExceptionSensorDataDaoImpl.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferHttpTimerDataDaoImpl.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferInvocationDataDaoImpl.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferSqlDataDaoImpl.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferTimerDataDaoImpl.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/impl/DefaultDataDaoImpl.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/impl/MethodIdentDaoImpl.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/impl/MethodIdentToSensorTypeDaoImpl.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/impl/MethodSensorTypeIdentDaoImpl.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/impl/PlatformIdentDaoImpl.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/impl/PlatformSensorTypeIdentDaoImpl.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/impl/StorageDataDaoImpl.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/dao/impl/TimerDataAggregator.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/indexing/impl/RootBranchFactory.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/jaxb/JAXBTransformator.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/jetty/FileUploadServlet.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/jetty/JettyWebApplicationContextInitializer.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/processor/AbstractChainedCmrDataProcessor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/processor/AbstractCmrDataProcessor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/processor/impl/BufferInserterCmrProcessor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/processor/impl/CacheIdGeneratorCmrProcessor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/processor/impl/ExceptionMessageCmrProcessor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/processor/impl/IndexerCmrProcessor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/processor/impl/InvocationModifierCmrProcessor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/processor/impl/RecorderCmrProcessor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/processor/impl/SessionInserterCmrProcessor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/processor/impl/SqlExclusiveTimeCmrProcessor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/processor/impl/SystemInformationDataCmrProcessor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/processor/impl/TimerDataChartingCmrProcessor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/property/PropertyManager.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/property/PropertyUpdateExecutor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/rmi/KryoNetServerCreator.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/service/AgentStorageService.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/service/CmrManagementService.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/service/ExceptionDataAccessService.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/service/GlobalDataAccessService.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/service/HttpTimerDataAccessService.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/service/InvocationDataAccessService.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/service/RegistrationService.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/service/ServerStatusService.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/service/SqlDataAccessService.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/service/StorageService.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/service/TimerDataAccessService.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/service/rest/CmrRestfulService.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/service/rest/StorageRestfulService.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/service/rest/error/JsonError.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/spring/aop/ExceptionInterceptor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/spring/aop/MethodLog.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/spring/aop/MethodLogInterceptor.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/spring/exporter/KryoHttpInvokerServiceExporter.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/spring/exporter/KryoNetRmiServiceExporter.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/spring/exporter/RemotingExporter.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/storage/CachingWriteDataProcessorProvider.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/storage/CmrStorageManager.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/storage/CmrStorageRecorder.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/storage/CmrStorageWriter.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/storage/CmrStorageWriterProvider.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/util/AgentStatusDataProvider.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/util/CacheIdGenerator.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/util/Converter.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/util/ExceptionEventType.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/util/HealthStatus.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/util/HibernateUtil.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/util/ListStringType.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/util/MapStringType.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/util/PlatformIdentCache.java create mode 100644 CMR/src/info/novatec/inspectit/cmr/util/ShutdownService.java create mode 100644 CMR/src/spring/spring-context-beans.xml create mode 100644 CMR/src/spring/spring-context-database.xml create mode 100644 CMR/src/spring/spring-context-global.xml create mode 100644 CMR/src/spring/spring-context-jetty.xml create mode 100644 CMR/src/spring/spring-context-processors.xml create mode 100644 CMR/src/spring/spring-context-rest.xml create mode 100644 CMR/startup.bat create mode 100644 CMR/startup.sh create mode 100644 CMR/test/info/novatec/inspectit/cmr/cache/impl/AtomicBufferTest.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/cache/impl/BufferPropertiesTest.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/cache/impl/MemoryCalculationTest.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/dao/impl/PlatformIdentDaoTest.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/dao/impl/TimerDataAggregatorTest.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/processor/impl/CmrDataProcessorsTest.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/property/PropertyIntegrationTest.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/property/PropertyManagerTest.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/property/PropertyUpdateExecutorTest.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/service/AgentStorageServiceTest.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/service/GlobalDataAccessServiceTest.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/service/RegistrationServiceTest.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/service/rest/StorageRestfulServiceTest.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/storage/CmrStorageManagerTest.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/storage/CmrStorageRecorderTest.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/test/AbstractTestNGLogSupport.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/test/AbstractTransactionalTestNGLogSupport.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/util/AgentStatusDataProviderTest.java create mode 100644 CMR/test/info/novatec/inspectit/cmr/util/PlatformIdentCacheTest.java create mode 100644 CMR/test/info/novatec/inspectit/storage/StorageIntegrationTest.java create mode 100644 CMR/test/info/novatec/inspectit/storage/serializer/impl/HibernateSerializerTest.java create mode 100644 CMR/test/info/novatec/inspectit/storage/serializer/schema/SchemaManagerTestProvider.java create mode 100644 CMR/test/spring/spring-context-storage-test.xml create mode 100644 Commons/.checkstyle create mode 100644 Commons/.classpath create mode 100644 Commons/.gitignore create mode 100644 Commons/.pmd create mode 100644 Commons/.project create mode 100644 Commons/.settings/org.apache.ivyde.eclipse.prefs create mode 100644 Commons/.settings/org.eclipse.jdt.core.prefs create mode 100644 Commons/.settings/org.eclipse.pde.core.prefs create mode 100644 Commons/.settings/org.eclipse.pde.prefs create mode 100644 Commons/META-INF/MANIFEST.MF create mode 100644 Commons/build.properties create mode 100644 Commons/resources/build.properties create mode 100644 Commons/resources/build.xml create mode 100644 Commons/resources/installer/installer-commons/Unix_shortcutSpec.xml create mode 100644 Commons/resources/installer/installer-commons/Win_shortcutSpec.xml create mode 100644 Commons/resources/installer/installer-commons/bin/langpacks/flags/eng-custom.gif create mode 100644 Commons/resources/installer/installer-commons/bin/langpacks/installer/eng-custom.xml create mode 100755 Commons/resources/installer/installer-commons/head-logo.png create mode 100644 Commons/resources/installer/installer-commons/install.xml create mode 100644 Commons/resources/installer/installer-commons/license.html create mode 100755 Commons/resources/installer/installer-commons/service/inspectITCMRw.exe create mode 100644 Commons/resources/installer/installer-commons/service/template_installService.bat create mode 100644 Commons/resources/installer/installer-commons/service/template_uninstallService.bat create mode 100755 Commons/resources/installer/installer-commons/service/win32/prunsrv.exe create mode 100755 Commons/resources/installer/installer-commons/service/win64/prunsrv.exe create mode 100755 Commons/resources/installer/installer-commons/side-logo.png create mode 100644 Commons/resources/installer/installer-commons/welcome-information.html create mode 100644 Commons/resources/ivy/ivy.xml create mode 100644 Commons/resources/shared/build/cmr-startup.properties create mode 100644 Commons/resources/shared/build/common-targets.properties create mode 100644 Commons/resources/shared/build/common-targets.xml create mode 100644 Commons/resources/shared/coding-templates/inspectit-cleanup.xml create mode 100644 Commons/resources/shared/coding-templates/inspectit-codetemplates.xml create mode 100644 Commons/resources/shared/coding-templates/inspectit-formatter.xml create mode 100644 Commons/resources/shared/config/checkstyle/checkstyle.xsl create mode 100644 Commons/resources/shared/config/checkstyle/inspectit-checkstyle.xml create mode 100644 Commons/resources/shared/config/cpd/cpdhtml.xslt create mode 100644 Commons/resources/shared/config/findbugs/fancy-hist.xsl create mode 100644 Commons/resources/shared/config/findbugs/findBugsExcludeFilter.xml create mode 100644 Commons/resources/shared/config/findbugs/findBugsIncludeFilter.xml create mode 100644 Commons/resources/shared/config/ivy/ivysettings.xml create mode 100644 Commons/resources/shared/config/pmd/pmd-report.xslt create mode 100644 Commons/resources/shared/config/pmd/pmd_rules.xml create mode 100644 Commons/resources/shared/config/pmd/sorttable.js create mode 100644 Commons/resources/testng/testng.xml create mode 100644 Commons/src/info/novatec/inspectit/cmr/cache/IObjectSizes.java create mode 100644 Commons/src/info/novatec/inspectit/cmr/model/MethodIdent.java create mode 100644 Commons/src/info/novatec/inspectit/cmr/model/MethodIdentToSensorType.java create mode 100644 Commons/src/info/novatec/inspectit/cmr/model/MethodSensorTypeIdent.java create mode 100644 Commons/src/info/novatec/inspectit/cmr/model/MethodSensorTypeIdentHelper.java create mode 100644 Commons/src/info/novatec/inspectit/cmr/model/PlatformIdent.java create mode 100644 Commons/src/info/novatec/inspectit/cmr/model/PlatformSensorTypeIdent.java create mode 100644 Commons/src/info/novatec/inspectit/cmr/model/SensorTypeIdent.java create mode 100644 Commons/src/info/novatec/inspectit/cmr/property/spring/PropertyUpdate.java create mode 100644 Commons/src/info/novatec/inspectit/cmr/service/IAgentStorageService.java create mode 100644 Commons/src/info/novatec/inspectit/cmr/service/ICachedDataService.java create mode 100644 Commons/src/info/novatec/inspectit/cmr/service/IRegistrationService.java create mode 100644 Commons/src/info/novatec/inspectit/cmr/service/ServiceExporterType.java create mode 100644 Commons/src/info/novatec/inspectit/cmr/service/ServiceInterface.java create mode 100644 Commons/src/info/novatec/inspectit/cmr/service/exception/ServiceException.java create mode 100644 Commons/src/info/novatec/inspectit/cmr/storage/util/IObjectCloner.java create mode 100644 Commons/src/info/novatec/inspectit/communication/DefaultData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/ExceptionEvent.java create mode 100644 Commons/src/info/novatec/inspectit/communication/IAggregatedData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/IIdsAwareAggregatedData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/MethodSensorData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/Sizeable.java create mode 100644 Commons/src/info/novatec/inspectit/communication/SystemSensorData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/comparator/AggregatedExceptionSensorDataComparatorEnum.java create mode 100644 Commons/src/info/novatec/inspectit/communication/comparator/DefaultDataComparatorEnum.java create mode 100644 Commons/src/info/novatec/inspectit/communication/comparator/ExceptionSensorDataComparatorEnum.java create mode 100644 Commons/src/info/novatec/inspectit/communication/comparator/HttpTimerDataComparatorEnum.java create mode 100644 Commons/src/info/novatec/inspectit/communication/comparator/IDataComparator.java create mode 100644 Commons/src/info/novatec/inspectit/communication/comparator/InvocationAwareDataComparatorEnum.java create mode 100644 Commons/src/info/novatec/inspectit/communication/comparator/InvocationSequenceDataComparatorEnum.java create mode 100644 Commons/src/info/novatec/inspectit/communication/comparator/MethodSensorDataComparatorEnum.java create mode 100644 Commons/src/info/novatec/inspectit/communication/comparator/ResultComparator.java create mode 100644 Commons/src/info/novatec/inspectit/communication/comparator/SqlStatementDataComparatorEnum.java create mode 100644 Commons/src/info/novatec/inspectit/communication/comparator/TimerDataComparatorEnum.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/AggregatedExceptionSensorData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/AggregatedHttpTimerData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/AggregatedSqlStatementData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/AggregatedTimerData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/ClassLoadingInformationData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/CompilationInformationData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/CpuInformationData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/DatabaseAggregatedTimerData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/ExceptionSensorData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/HttpTimerData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/HttpTimerDataHelper.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/InvocationAwareData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/InvocationSequenceData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/InvocationSequenceDataHelper.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/MemoryInformationData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/ParameterContentData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/ParameterContentType.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/RuntimeInformationData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/SqlStatementData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/SystemInformationData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/ThreadInformationData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/TimerData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/VmArgumentData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/cmr/AgentStatusData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/data/cmr/CmrStatusData.java create mode 100644 Commons/src/info/novatec/inspectit/communication/valueobject/TimerRawVO.java create mode 100644 Commons/src/info/novatec/inspectit/indexing/IIndexQuery.java create mode 100644 Commons/src/info/novatec/inspectit/indexing/restriction/IIndexQueryRestriction.java create mode 100644 Commons/src/info/novatec/inspectit/kryonet/Client.java create mode 100644 Commons/src/info/novatec/inspectit/kryonet/Connection.java create mode 100644 Commons/src/info/novatec/inspectit/kryonet/EndPoint.java create mode 100644 Commons/src/info/novatec/inspectit/kryonet/ExtendedSerializationImpl.java create mode 100644 Commons/src/info/novatec/inspectit/kryonet/IExtendedSerialization.java create mode 100644 Commons/src/info/novatec/inspectit/kryonet/Listener.java create mode 100644 Commons/src/info/novatec/inspectit/kryonet/Serialization.java create mode 100644 Commons/src/info/novatec/inspectit/kryonet/Server.java create mode 100644 Commons/src/info/novatec/inspectit/kryonet/TcpConnection.java create mode 100644 Commons/src/info/novatec/inspectit/kryonet/UdpConnection.java create mode 100644 Commons/src/info/novatec/inspectit/kryonet/rmi/ObjectSpace.java create mode 100644 Commons/src/info/novatec/inspectit/spring/logger/Log.java create mode 100644 Commons/src/info/novatec/inspectit/spring/logger/LoggerPostProcessor.java create mode 100644 Commons/src/info/novatec/inspectit/storage/nio/ByteBufferProvider.java create mode 100644 Commons/src/info/novatec/inspectit/storage/nio/bytebuffer/ByteBufferFactory.java create mode 100644 Commons/src/info/novatec/inspectit/storage/nio/stream/AbstractExtendedByteBufferInputStream.java create mode 100644 Commons/src/info/novatec/inspectit/storage/nio/stream/ExtendedByteBufferOutputStream.java create mode 100644 Commons/src/info/novatec/inspectit/storage/nio/stream/SocketExtendedByteBufferInputStream.java create mode 100644 Commons/src/info/novatec/inspectit/storage/nio/stream/StreamProvider.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/HibernateAwareClassResolver.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/IKryoProvider.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/ISerializer.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/ISerializerProvider.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/SerializationException.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/impl/CustomCompatibleFieldSerializer.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/impl/HibernateAwareCollectionSerializer.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/impl/HibernateAwareMapSerializer.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/impl/HibernateProxySerializer.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/impl/InvocationAwareDataSerializer.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/impl/InvocationSequenceCustomCompatibleFieldSerializer.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/impl/SerializationManager.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/impl/StackTraceElementSerializer.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/impl/TimestampSerializer.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/provider/SerializationManagerProvider.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/schema/ClassSchema.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/schema/ClassSchemaManager.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/util/KryoSerializationPreferences.java create mode 100644 Commons/src/info/novatec/inspectit/storage/serializer/util/KryoUtil.java create mode 100644 Commons/src/info/novatec/inspectit/util/ArrayUtil.java create mode 100644 Commons/src/info/novatec/inspectit/util/IHibernateUtil.java create mode 100644 Commons/src/info/novatec/inspectit/util/KryoNetNetwork.java create mode 100644 Commons/src/info/novatec/inspectit/util/ObjectUtils.java create mode 100644 Commons/src/info/novatec/inspectit/util/TimeFrame.java create mode 100644 Commons/src/info/novatec/inspectit/util/UnderlyingSystemInfo.java create mode 100644 Commons/src/info/novatec/inspectit/versioning/FileBasedVersioningServiceImpl.java create mode 100644 Commons/src/info/novatec/inspectit/versioning/IVersioningService.java create mode 100644 Commons/src/schema/AbstractCustomStorageLabelType.sch create mode 100644 Commons/src/schema/AbstractStorageLabelType.sch create mode 100644 Commons/src/schema/AggregatedExceptionSensorData.sch create mode 100644 Commons/src/schema/AggregatedHttpTimerData.sch create mode 100644 Commons/src/schema/AggregatedSqlStatementData.sch create mode 100644 Commons/src/schema/AggregatedTimerData.sch create mode 100644 Commons/src/schema/ArrayBasedStorageLeaf.sch create mode 100644 Commons/src/schema/BooleanStorageLabel.sch create mode 100644 Commons/src/schema/ClassLoadingInformationData.sch create mode 100644 Commons/src/schema/CompilationInformationData.sch create mode 100644 Commons/src/schema/CpuInformationData.sch create mode 100644 Commons/src/schema/DateStorageLabel.sch create mode 100644 Commons/src/schema/ExceptionSensorData.sch create mode 100644 Commons/src/schema/HttpTimerData.sch create mode 100644 Commons/src/schema/InvocationSequenceData.sch create mode 100644 Commons/src/schema/LeafWithNoDescriptors.sch create mode 100644 Commons/src/schema/LocalStorageData.sch create mode 100644 Commons/src/schema/MemoryInformationData.sch create mode 100644 Commons/src/schema/MethodIdent.sch create mode 100644 Commons/src/schema/MethodIdentToSensorType.sch create mode 100644 Commons/src/schema/MethodSensorTypeIdent.sch create mode 100644 Commons/src/schema/NumberStorageLabel.sch create mode 100644 Commons/src/schema/ObjectStorageLabel.sch create mode 100644 Commons/src/schema/ParameterContentData.sch create mode 100644 Commons/src/schema/PlatformIdent.sch create mode 100644 Commons/src/schema/RuntimeInformationData.sch create mode 100644 Commons/src/schema/SensorTypeIdent.sch create mode 100644 Commons/src/schema/SimpleStorageDescriptor.sch create mode 100644 Commons/src/schema/SqlStatementData.sch create mode 100644 Commons/src/schema/StorageBranch.sch create mode 100644 Commons/src/schema/StorageBranchIndexer.sch create mode 100644 Commons/src/schema/StorageData.sch create mode 100644 Commons/src/schema/StringStorageLabel.sch create mode 100644 Commons/src/schema/SystemInformationData.sch create mode 100644 Commons/src/schema/ThreadInformationData.sch create mode 100644 Commons/src/schema/TimeFrame.sch create mode 100644 Commons/src/schema/TimerData.sch create mode 100644 Commons/src/schema/TimestampIndexer.sch create mode 100644 Commons/src/schema/VmArgumentData.sch create mode 100644 Commons/src/schema/schemaList.txt create mode 100644 Commons/test/info/novatec/inspectit/cmr/cache/impl/ObjectSizesPrimitiveTypesSizeTest.java create mode 100644 Commons/test/info/novatec/inspectit/communication/EqualsVerifierTest.java create mode 100644 Commons/test/info/novatec/inspectit/communication/data/AggregatedDataTest.java create mode 100644 Commons/test/info/novatec/inspectit/communication/data/InvocationAwareDataTest.java create mode 100644 Commons/test/info/novatec/inspectit/communication/valueobject/TimerRawVOTest.java create mode 100644 Commons/test/info/novatec/inspectit/storage/nio/stream/SocketExtendedByteBufferInputStreamTest.java create mode 100644 Commons/test/info/novatec/inspectit/storage/serializer/impl/BackwardForwardCompatibilityTest.java create mode 100644 CommonsCS/.checkstyle create mode 100644 CommonsCS/.classpath create mode 100644 CommonsCS/.gitignore create mode 100644 CommonsCS/.pmd create mode 100644 CommonsCS/.project create mode 100644 CommonsCS/.settings/org.apache.ivyde.eclipse.prefs create mode 100644 CommonsCS/.settings/org.eclipse.jdt.core.prefs create mode 100644 CommonsCS/.settings/org.eclipse.pde.core.prefs create mode 100644 CommonsCS/.settings/org.eclipse.pde.prefs create mode 100644 CommonsCS/META-INF/MANIFEST.MF create mode 100644 CommonsCS/build.properties create mode 100644 CommonsCS/resources/build.properties create mode 100644 CommonsCS/resources/build.xml create mode 100644 CommonsCS/resources/ivy/ivy.xml create mode 100644 CommonsCS/resources/testng/testng.xml create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/AbstractProperty.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/Configuration.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/GroupedProperty.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/PropertySection.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/SingleProperty.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/impl/BooleanProperty.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/impl/ByteProperty.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/impl/LongProperty.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/impl/PercentageProperty.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/impl/StringProperty.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/package-info.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/validation/PropertyValidation.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/validation/PropertyValidationException.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/validation/ValidationError.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/validator/AbstractComparingValidator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/validator/AbstractSinglePropertyValidator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/validator/IGroupedProperyValidator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/validator/ISinglePropertyValidator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/validator/impl/FullyQualifiedClassNameValidator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/validator/impl/GreaterOrEqualValidator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/validator/impl/GreaterValidator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/validator/impl/LessOrEqualValidator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/validator/impl/LessValidator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/validator/impl/NegativeValidator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/validator/impl/NotEmptyValidator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/validator/impl/PercentageValidator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/configuration/validator/impl/PositiveValidator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/update/AbstractPropertyUpdate.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/update/IPropertyUpdate.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/update/configuration/ConfigurationUpdate.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/update/impl/BooleanPropertyUpdate.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/update/impl/BytePropertyUpdate.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/update/impl/LongPropertyUpdate.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/update/impl/PercentagePropertyUpdate.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/update/impl/RestoreDefaultPropertyUpdate.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/update/impl/StringPropertyUpdate.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/property/update/package-info.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/service/ICmrManagementService.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/service/IExceptionDataAccessService.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/service/IGlobalDataAccessService.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/service/IHttpTimerDataAccessService.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/service/IInvocationDataAccessService.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/service/IServerStatusService.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/service/ISqlDataAccessService.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/service/IStorageService.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/service/ITimerDataAccessService.java create mode 100644 CommonsCS/src/info/novatec/inspectit/cmr/service/cache/CachedDataService.java create mode 100644 CommonsCS/src/info/novatec/inspectit/communication/data/cmr/RecordingData.java create mode 100644 CommonsCS/src/info/novatec/inspectit/communication/data/cmr/WritingStatus.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/AbstractBranch.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/ITreeComponent.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/aggregation/Aggregators.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/aggregation/IAggregator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/aggregation/impl/AggregationPerformer.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/aggregation/impl/ClassLoadingInformationDataAggregator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/aggregation/impl/CpuInformationDataAggregator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/aggregation/impl/ExceptionDataAggregator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/aggregation/impl/HttpTimerDataAggregator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/aggregation/impl/MemoryInformationDataAggregator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/aggregation/impl/SqlStatementDataAggregator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/aggregation/impl/ThreadInformationDataAggregator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/aggregation/impl/TimerDataAggregator.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/buffer/IBufferBranchIndexer.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/buffer/IBufferTreeComponent.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/buffer/impl/Branch.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/buffer/impl/BufferBranchIndexer.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/buffer/impl/Leaf.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/impl/IndexQuery.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/impl/IndexingException.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/indexer/AbstractSharedInstanceBranchIndexer.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/indexer/IBranchIndexer.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/indexer/impl/InvocationChildrenIndexer.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/indexer/impl/MethodIdentIndexer.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/indexer/impl/ObjectTypeIndexer.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/indexer/impl/PlatformIdentIndexer.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/indexer/impl/SensorTypeIdentIndexer.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/indexer/impl/SqlStringIndexer.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/indexer/impl/TimestampIndexer.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/query/factory/AbstractQueryFactory.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/query/factory/impl/ExceptionSensorDataQueryFactory.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/query/factory/impl/HttpTimerDataQueryFactory.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/query/factory/impl/InvocationSequenceDataQueryFactory.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/query/factory/impl/SqlStatementDataQueryFactory.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/query/factory/impl/TimerDataQueryFactory.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/query/provider/IIndexQueryProvider.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/query/provider/impl/IndexQueryProvider.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/query/provider/impl/StorageIndexQueryProvider.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/restriction/AbstractIndexQueryRestriction.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/restriction/IIndexQueryRestrictionProcessor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/restriction/impl/CachingIndexQueryRestrictionProcessor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/restriction/impl/ComparableIndexQueryRestriction.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/restriction/impl/IndexQueryRestrictionFactory.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/restriction/impl/ObjectIndexQueryRestriction.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/storage/AbstractStorageDescriptor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/storage/IStorageBranchIndexer.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/storage/IStorageDescriptor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/storage/IStorageTreeComponent.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/storage/impl/ArrayBasedStorageLeaf.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/storage/impl/CombinedStorageBranch.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/storage/impl/LeafWithNoDescriptors.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/storage/impl/SimpleStorageDescriptor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/storage/impl/StorageBranch.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/storage/impl/StorageBranchIndexer.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/storage/impl/StorageDescriptor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/storage/impl/StorageIndexQuery.java create mode 100644 CommonsCS/src/info/novatec/inspectit/indexing/storage/impl/StorageRootBranchFactory.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/AbstractStorageData.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/IStorageData.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/IWriter.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/LocalStorageData.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/StorageData.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/StorageException.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/StorageFileType.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/StorageIndexingTreeHandler.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/StorageManager.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/StorageWriter.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/AbstractStorageLabel.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/BooleanStorageLabel.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/DateStorageLabel.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/NumberStorageLabel.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/ObjectStorageLabel.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/StringStorageLabel.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/management/AbstractLabelManagementAction.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/management/impl/AddLabelManagementAction.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/management/impl/RemoveLabelManagementAction.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/type/AbstractCustomStorageLabelType.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/type/AbstractStorageLabelType.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/type/impl/AssigneeLabelType.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/type/impl/CreationDateLabelType.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/type/impl/CustomBooleanLabelType.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/type/impl/CustomDateLabelType.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/type/impl/CustomNumberLabelType.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/type/impl/CustomStringLabelType.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/type/impl/DataTimeFrameLabelType.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/type/impl/ExploredByLabelType.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/type/impl/RatingLabelType.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/type/impl/StatusLabelType.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/label/type/impl/UseCaseLabelType.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/nio/AbstractChannelManager.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/nio/CustomAsyncChannel.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/nio/WriteReadAttachment.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/nio/WriteReadCompletionRunnable.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/nio/read/ReadingChannelManager.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/nio/read/ReadingCompletionHandler.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/nio/stream/ExtendedByteBufferInputStream.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/nio/stream/InputStreamProvider.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/nio/write/WritingChannelManager.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/nio/write/WritingCompletionHandler.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/processor/AbstractChainedDataProcessor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/processor/AbstractDataProcessor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/processor/AbstractExtractorDataProcessor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/processor/impl/AgentFilterDataProcessor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/processor/impl/DataAggregatorProcessor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/processor/impl/DataSaverProcessor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/processor/impl/InvocationClonerDataProcessor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/processor/impl/InvocationExtractorDataProcessor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/processor/impl/TimeFrameDataProcessor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/processor/write/AbstractWriteDataProcessor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/processor/write/impl/QueryCachingDataProcessor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/recording/RecordingProperties.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/recording/RecordingState.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/serializer/SerializationManagerPostProcessor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/serializer/impl/ServerStatusSerializer.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/util/CopyMoveFileVisitor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/util/DeleteFileVisitor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/util/ExecutorServiceFactory.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/util/RangeDescriptor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/util/StorageDeleteFileVisitor.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/util/StorageIndexTreeProvider.java create mode 100644 CommonsCS/src/info/novatec/inspectit/storage/util/StorageUtil.java create mode 100644 CommonsCS/test/info/novatec/inspectit/cmr/property/XmlTransformationTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/cmr/property/configuration/ConfigurationTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/cmr/property/configuration/GroupedPropertyTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/cmr/property/configuration/SinglePropertyTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/cmr/property/configuration/validator/ValidatorsTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/cmr/property/update/ConfigurationUpdateTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/cmr/service/cache/CachedDataServiceTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/indexing/aggregation/impl/HttpDataAggregatorTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/indexing/buffer/impl/BranchTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/indexing/buffer/impl/BufferBranchIndexerTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/indexing/impl/BufferIndexingTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/indexing/impl/StorageIndexingTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/indexing/indexer/impl/BranchIndexersTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/indexing/restriction/IndexQueryRestrictionProcessorTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/indexing/storage/impl/ArrayBasedStorageLeafTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/indexing/storage/impl/StorageBranchIndexerTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/storage/StorageDataTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/storage/StorageIndexingTreeHandlerTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/storage/StorageWriterTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/storage/label/management/impl/LabelManagementActionsTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/storage/nio/ByteBufferProviderTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/storage/nio/ChannelManagersTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/storage/nio/CompletionHandlersTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/storage/nio/stream/ExtendedByteBufferInputStreamTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/storage/nio/stream/ExtendedByteBufferOutputStreamTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/storage/processor/impl/StorageDataProcessorsTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/storage/serializer/impl/SerializerTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/storage/serializer/schema/SchemaManagerTestProvider.java create mode 100644 CommonsCS/test/info/novatec/inspectit/storage/serializer/schema/SchemaTest.java create mode 100644 CommonsCS/test/info/novatec/inspectit/storage/util/FileVisitorsTest.java create mode 100644 LICENSE.txt create mode 100644 LICENSEEXCEPTIONS.txt create mode 100644 THIRDPARTYLICENSE.txt create mode 100644 inspectIT/.checkstyle create mode 100644 inspectIT/.classpath create mode 100644 inspectIT/.gitignore create mode 100644 inspectIT/.pmd create mode 100644 inspectIT/.project create mode 100644 inspectIT/.settings/org.apache.ivyde.eclipse.prefs create mode 100644 inspectIT/.settings/org.eclipse.jdt.core.prefs create mode 100644 inspectIT/.settings/org.eclipse.jdt.ui.prefs create mode 100644 inspectIT/.settings/org.eclipse.pde.prefs create mode 100644 inspectIT/META-INF/MANIFEST.MF create mode 100644 inspectIT/META-INF/spring/spring-context-model-main.xml create mode 100644 inspectIT/META-INF/spring/spring-context-model-osgi.xml create mode 100644 inspectIT/META-INF/spring/spring-context-model-other.xml create mode 100644 inspectIT/META-INF/spring/spring-context-model-storage.xml create mode 100644 inspectIT/about.png create mode 100644 inspectIT/build.properties create mode 100644 inspectIT/content/introContent.xml create mode 100644 inspectIT/content/static/documentation.html create mode 100644 inspectIT/content/static/fonts/Abel.woff create mode 100644 inspectIT/content/static/graphics/background.jpg create mode 100644 inspectIT/content/static/graphics/backgroundDown.png create mode 100644 inspectIT/content/static/graphics/backgroundMenu.png create mode 100644 inspectIT/content/static/graphics/documentationActive.png create mode 100644 inspectIT/content/static/graphics/documentationInactive.png create mode 100644 inspectIT/content/static/graphics/inspectit_logo.png create mode 100644 inspectIT/content/static/graphics/inspectit_slogan.png create mode 100644 inspectIT/content/static/graphics/newFeaturesActive.png create mode 100644 inspectIT/content/static/graphics/newFeaturesInactive.png create mode 100644 inspectIT/content/static/graphics/novatec_logo.png create mode 100644 inspectIT/content/static/graphics/produktkarton_inspectit_small.png create mode 100644 inspectIT/content/static/graphics/welcomeActive.png create mode 100644 inspectIT/content/static/graphics/welcomeInactive.png create mode 100644 inspectIT/content/static/icons/communityIcon.png create mode 100644 inspectIT/content/static/icons/contactUsIcon.png create mode 100644 inspectIT/content/static/icons/documentationIcon.png create mode 100644 inspectIT/content/static/icons/export.png create mode 100644 inspectIT/content/static/icons/homeIcon.png create mode 100644 inspectIT/content/static/icons/newFeaturesIcon.png create mode 100644 inspectIT/content/static/icons/supportIcon.png create mode 100644 inspectIT/content/static/main.css create mode 100644 inspectIT/content/static/newFeatures.html create mode 100644 inspectIT/content/static/welcome.html create mode 100644 inspectIT/icons/eclipse/LICENSE-NOTICE.txt create mode 100644 inspectIT/icons/eclipse/add_obj.gif create mode 100644 inspectIT/icons/eclipse/agent.gif create mode 100644 inspectIT/icons/eclipse/agent_active.gif create mode 100644 inspectIT/icons/eclipse/agent_na.gif create mode 100644 inspectIT/icons/eclipse/agent_not_sending.gif create mode 100644 inspectIT/icons/eclipse/alert_obj.gif create mode 100644 inspectIT/icons/eclipse/all_instances.gif create mode 100644 inspectIT/icons/eclipse/buffer_clear.gif create mode 100644 inspectIT/icons/eclipse/buffer_copy.gif create mode 100644 inspectIT/icons/eclipse/build.gif create mode 100644 inspectIT/icons/eclipse/business.gif create mode 100644 inspectIT/icons/eclipse/calendar.gif create mode 100644 inspectIT/icons/eclipse/call_hierarchy.gif create mode 100644 inspectIT/icons/eclipse/catalog.gif create mode 100644 inspectIT/icons/eclipse/class_obj.gif create mode 100644 inspectIT/icons/eclipse/collapseall.gif create mode 100644 inspectIT/icons/eclipse/complete_status.gif create mode 100644 inspectIT/icons/eclipse/dates.gif create mode 100644 inspectIT/icons/eclipse/debugt_obj.gif create mode 100644 inspectIT/icons/eclipse/debugtt_obj.gif create mode 100644 inspectIT/icons/eclipse/defaultview_misc.gif create mode 100644 inspectIT/icons/eclipse/delete_obj.gif create mode 100644 inspectIT/icons/eclipse/disabled_co.gif create mode 100644 inspectIT/icons/eclipse/discovery.gif create mode 100644 inspectIT/icons/eclipse/edit.gif create mode 100644 inspectIT/icons/eclipse/exceptiontracer.gif create mode 100644 inspectIT/icons/eclipse/exceptiontree.gif create mode 100644 inspectIT/icons/eclipse/export.gif create mode 100644 inspectIT/icons/eclipse/filter_ps.gif create mode 100644 inspectIT/icons/eclipse/flag.gif create mode 100644 inspectIT/icons/eclipse/font.gif create mode 100644 inspectIT/icons/eclipse/gel_sc_obj.gif create mode 100644 inspectIT/icons/eclipse/graph_bar.gif create mode 100644 inspectIT/icons/eclipse/graph_pie.gif create mode 100644 inspectIT/icons/eclipse/help.gif create mode 100644 inspectIT/icons/eclipse/home_nav.gif create mode 100644 inspectIT/icons/eclipse/ihigh_obj.gif create mode 100644 inspectIT/icons/eclipse/import.gif create mode 100644 inspectIT/icons/eclipse/info_obj.gif create mode 100644 inspectIT/icons/eclipse/insp_sbook.gif create mode 100644 inspectIT/icons/eclipse/label.gif create mode 100644 inspectIT/icons/eclipse/label_add.gif create mode 100644 inspectIT/icons/eclipse/label_delete.gif create mode 100644 inspectIT/icons/eclipse/locate_in_hierarchy.gif create mode 100644 inspectIT/icons/eclipse/message.gif create mode 100644 inspectIT/icons/eclipse/methdef_obj.gif create mode 100644 inspectIT/icons/eclipse/method.gif create mode 100644 inspectIT/icons/eclipse/method_time.gif create mode 100644 inspectIT/icons/eclipse/methpri_obj.gif create mode 100644 inspectIT/icons/eclipse/methpro_obj.gif create mode 100644 inspectIT/icons/eclipse/methpub_obj.gif create mode 100644 inspectIT/icons/eclipse/next_nav.gif create mode 100644 inspectIT/icons/eclipse/number.gif create mode 100644 inspectIT/icons/eclipse/options.gif create mode 100644 inspectIT/icons/eclipse/over_co.gif create mode 100644 inspectIT/icons/eclipse/overlay_error.gif create mode 100644 inspectIT/icons/eclipse/overlay_priority.gif create mode 100644 inspectIT/icons/eclipse/package_obj.gif create mode 100644 inspectIT/icons/eclipse/preferences.gif create mode 100644 inspectIT/icons/eclipse/prev_nav.gif create mode 100644 inspectIT/icons/eclipse/prj_obj.gif create mode 100644 inspectIT/icons/eclipse/properties.gif create mode 100644 inspectIT/icons/eclipse/pview.gif create mode 100644 inspectIT/icons/eclipse/read_obj.gif create mode 100644 inspectIT/icons/eclipse/record.gif create mode 100644 inspectIT/icons/eclipse/record_gray.gif create mode 100644 inspectIT/icons/eclipse/record_schedule.gif create mode 100644 inspectIT/icons/eclipse/record_term.gif create mode 100644 inspectIT/icons/eclipse/refresh.gif create mode 100644 inspectIT/icons/eclipse/remove_co.gif create mode 100644 inspectIT/icons/eclipse/remove_correction.gif create mode 100644 inspectIT/icons/eclipse/remove_exc.gif create mode 100644 inspectIT/icons/eclipse/server_instance.gif create mode 100644 inspectIT/icons/eclipse/showcat_co.gif create mode 100644 inspectIT/icons/eclipse/stacktrace.gif create mode 100644 inspectIT/icons/eclipse/start_task.gif create mode 100644 inspectIT/icons/eclipse/storage.gif create mode 100644 inspectIT/icons/eclipse/storage_available.gif create mode 100644 inspectIT/icons/eclipse/storage_download.gif create mode 100644 inspectIT/icons/eclipse/storage_finalize.gif create mode 100644 inspectIT/icons/eclipse/storage_na.gif create mode 100644 inspectIT/icons/eclipse/storage_overlay.gif create mode 100644 inspectIT/icons/eclipse/storage_readable.gif create mode 100644 inspectIT/icons/eclipse/storage_recording.gif create mode 100644 inspectIT/icons/eclipse/storage_upload.gif create mode 100644 inspectIT/icons/eclipse/storage_writable.gif create mode 100644 inspectIT/icons/eclipse/term_restart.gif create mode 100644 inspectIT/icons/eclipse/terminate_co.gif create mode 100644 inspectIT/icons/eclipse/time.gif create mode 100644 inspectIT/icons/eclipse/time_delta.gif create mode 100644 inspectIT/icons/eclipse/timeframe.gif create mode 100644 inspectIT/icons/eclipse/transform.gif create mode 100644 inspectIT/icons/eclipse/trash.gif create mode 100644 inspectIT/icons/eclipse/url.gif create mode 100644 inspectIT/icons/eclipse/user.gif create mode 100644 inspectIT/icons/eclipse/warning_obj.gif create mode 100644 inspectIT/icons/eclipse/wizban/add_wiz.png create mode 100644 inspectIT/icons/eclipse/wizban/download_wiz.png create mode 100644 inspectIT/icons/eclipse/wizban/edit_wiz.png create mode 100644 inspectIT/icons/eclipse/wizban/export_wiz.png create mode 100644 inspectIT/icons/eclipse/wizban/import_wiz.png create mode 100644 inspectIT/icons/eclipse/wizban/label_wiz.png create mode 100644 inspectIT/icons/eclipse/wizban/record_wiz.png create mode 100644 inspectIT/icons/eclipse/wizban/server_wiz.png create mode 100644 inspectIT/icons/eclipse/wizban/storage_wiz.png create mode 100644 inspectIT/icons/eclipse/wizban/upload_wiz.png create mode 100644 inspectIT/icons/eclipse/workset.gif create mode 100644 inspectIT/icons/fugue/LICENSE-NOTICE.txt create mode 100644 inspectIT/icons/fugue/arrow-switch.png create mode 100644 inspectIT/icons/fugue/blue-document-tree.png create mode 100644 inspectIT/icons/fugue/database-sql.png create mode 100644 inspectIT/icons/fugue/document-tree.png create mode 100644 inspectIT/icons/fugue/memory.png create mode 100644 inspectIT/icons/fugue/processor.png create mode 100644 inspectIT/icons/fugue/resource-monitor.png create mode 100644 inspectIT/icons/fugue/system-monitor.png create mode 100644 inspectIT/icons/selfmade/128x128.png create mode 100644 inspectIT/icons/selfmade/16x16.png create mode 100644 inspectIT/icons/selfmade/16x16_32bit.bmp create mode 100644 inspectIT/icons/selfmade/32x32.png create mode 100644 inspectIT/icons/selfmade/32x32_32bit.bmp create mode 100644 inspectIT/icons/selfmade/48x48.png create mode 100644 inspectIT/icons/selfmade/48x48_32bit.bmp create mode 100644 inspectIT/icons/selfmade/64x64.png create mode 100644 inspectIT/icons/selfmade/field.png create mode 100644 inspectIT/icons/selfmade/inspectIT.icns create mode 100644 inspectIT/icons/selfmade/inspectIT.ico create mode 100644 inspectIT/icons/selfmade/inspectIT.xpm create mode 100644 inspectIT/icons/selfmade/parameter.png create mode 100644 inspectIT/icons/selfmade/return.png create mode 100644 inspectIT/icons/selfmade/server_add.png create mode 100644 inspectIT/icons/selfmade/server_offline.png create mode 100644 inspectIT/icons/selfmade/server_offline_16x16.png create mode 100644 inspectIT/icons/selfmade/server_online.png create mode 100644 inspectIT/icons/selfmade/server_online_16x16.png create mode 100644 inspectIT/icons/selfmade/server_refresh.png create mode 100644 inspectIT/icons/selfmade/server_refresh_16x16.png create mode 100644 inspectIT/icons/selfmade/server_remove.png create mode 100644 inspectIT/inspectIT.product create mode 100644 inspectIT/inspectIT.target create mode 100644 inspectIT/logging-config.xml create mode 100644 inspectIT/plugin.properties create mode 100644 inspectIT/plugin.xml create mode 100644 inspectIT/plugin_customization.ini create mode 100644 inspectIT/release/build.properties create mode 100644 inspectIT/release/build.xml create mode 100644 inspectIT/release/resources/ivy/ivy.xml create mode 100644 inspectIT/release/resources/testng/testng.xml create mode 100644 inspectIT/splash.bmp create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/Application.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/ApplicationWorkbenchAdvisor.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/ApplicationWorkbenchWindowAdvisor.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/InspectIT.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/InspectITConstants.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/InspectITImages.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/Perspective.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/action/MenuAction.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/composite/BreadcrumbTitleComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/composite/StorageInfoComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/details/DetailsCellContent.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/details/DetailsGenerationFactory.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/details/DetailsTable.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/details/YesNoDetailsCellContent.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/details/generator/IDetailsGenerator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/AggregatedDurationDetailsGenerator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/DurationDetailsGenerator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/ExceptionDetailsGenerator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/GeneralInfoDetailsGenerator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/HttpDetailsGenerator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/InvocationAffiliationDetailsGenerator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/InvocationSequenceDetailsGenerator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/MethodInfoDetailsGenerator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/ParameterContentDetailsGenerator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/SqlDetailsGenerator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/dialog/AddCmrRepositoryDefinitionDialog.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/dialog/DetailsDialog.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/dialog/EditRepositoryDataDialog.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/AbstractSubView.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/ISubView.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/SubViewFactory.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/composite/AbstractCompositeSubView.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/composite/GridCompositeSubView.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/composite/SashCompositeSubView.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/composite/TabbedCompositeSubView.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/graph/GraphSubView.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/graph/PlotFactory.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/AbstractPlotController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/AbstractTimerDataPlotController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DateAxisZoomNotify.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DefaultClassesPlotController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DefaultCpuPlotController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DefaultMemoryPlotController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DefaultThreadsPlotController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/HttpTimerPlotController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/PlotController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/TimerPlotController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/YIntervalSeriesImproved.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/ZoomListener.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/EditorPropertiesData.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/InputDefinition.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/CombinedInvocationsInputDefinitionExtra.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/ExceptionTypeInputDefinitionExtra.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/HttpChartingInputDefinitionExtra.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/IInputDefinitionExtra.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/InputDefinitionExtrasMarkerFactory.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/NavigationSteppingInputDefinitionExtra.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/SqlStatementInputDefinitionExtra.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/TimerDataChartingInputDefinitionExtra.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/FormPreferencePanel.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/IPreferenceGroup.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/IPreferencePanel.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/PreferenceControlFactory.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/PreferenceEventCallback.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/PreferenceId.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/AbstractPreferenceControl.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/IPreferenceControl.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/SamplingRateControl.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/SamplingRateSelecterFactory.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/TimeLineControl.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/samplingrate/ISamplingRateMode.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/samplingrate/SamplingRateMode.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/root/AbstractRootEditor.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/root/FormRootEditor.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/root/IRootEditor.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/root/MultiSubViewSelectionProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/root/RootEditorInput.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/root/SubViewClassificationController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/search/ISearchExecutor.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/search/OpenedSearchControlCache.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/search/SearchControl.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/search/criteria/SearchCriteria.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/search/criteria/SearchResult.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/search/factory/SearchFactory.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/search/helper/AbstractSearchHelper.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/search/helper/DeferredTreeViewerSearchHelper.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/search/helper/TableViewerSearchHelper.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/RemoteTableViewerComparator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/TableSubView.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/TableViewerComparator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/AbstractHttpInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/AbstractTableInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/AggregatedTimerSummaryInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/ExceptionSensorInvocInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/GroupedExceptionOverviewInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/HttpTimerDataInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/InvocOverviewInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/MethodInvocInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/MultiInvocDataInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/NavigationInvocOverviewInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/SqlParameterAggregationInputControler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/TableInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/TaggedHttpTimerDataInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/TimerDataInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/UngroupedExceptionOverviewInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/testers/ActiveSubViewTester.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/testers/AddToSteppingObjectsTester.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/testers/MaximizeMinimizeTester.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/testers/NavigationTester.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/testers/SubViewClassificationTester.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/text/TextSubView.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/AbstractTextInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/ClassesInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/CpuInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/MemoryInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/SqlInvocSummaryTextInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/SqlStatementTextInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/TextInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/ThreadsInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/UngroupedExceptionOverviewStackTraceInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/VmSummaryInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tooltip/ColumnAwareToolTipSupport.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tooltip/IColumnToolTipProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tree/DeferredTreeViewer.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tree/SteppingTreeSubView.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tree/TreeSubView.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tree/TreeViewerComparator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/AbstractTreeInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/DeferredInvoc.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/DeferredInvocAdapterFactory.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/ExceptionMessagesTreeInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/ExceptionTreeInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/InvocDetailInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/SqlInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/SqlInvocInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/SteppingInvocDetailInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/SteppingTreeInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/TreeInputController.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/tree/util/DatabaseSqlTreeComparator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/viewers/AbstractViewerComparator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/viewers/CheckedDelegatingIndexLabelProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/viewers/RawAggregatedResultComparator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/editor/viewers/StyledCellIndexLabelProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/filter/FilterComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/form/CmrRepositoryPropertyForm.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/form/StorageDataPropertyForm.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/formatter/ColorFormatter.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/formatter/ImageFormatter.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/formatter/NumberFormatter.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/formatter/SensorTypeAvailabilityEnum.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/formatter/TextFormatter.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/AbstractTemplateHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/AddStorageLabelHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/ClearRepositoryBufferHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/CloseAndShowStorageHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/CloseStorageHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/CmrConfigurationHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/CopyBufferToStorageHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/CopyDataToStorageHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/CopySqlQueryHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/DeleteAgentHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/DeleteLocalStorageHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/DeleteStorageHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/DetailsHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/DownloadStorageHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/EditCmrRepositoryHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/EditStorageDataHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/EmptyHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/ExportLocalStorageHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/FindHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/HttpDisplayInChartHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/InvocationsCombineDataHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/LocateHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/MaximizeActiveViewHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToAggregatedSqlDataHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToAggregatedTimerDataHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToExceptionTypeHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToInvocationsHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToPlotting.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToStartMethodInvocationHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/OpenUrlHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/OpenViewHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/RefreshEditorHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/RefreshViewHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/RemoveCmrRepositoryHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/RemoveStorageLabelHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/RenameEditorHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/ShowHideColumnsHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/ShowLabelsManagerHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/ShowRepositoryHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/ShutdownCmrHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/StartRecordingHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/StopRecordingHanlder.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/TableCopyHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/TreeCollapseHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/TreeCopyHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/TreeExpandHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/handlers/UploadStorageHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/log/LogListener.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/menu/SearchDocumentationContributionItem.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/menu/ShowHideMenuManager.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/AccessFlag.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/AgentFolderFactory.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/AgentLeaf.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/Component.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/Composite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/DeferredAgentsComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/DeferredBrowserComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/DeferredClassComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/DeferredComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/DeferredMethodComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/DeferredPackageComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/DeferredType.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/ExceptionImageFactory.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/FilteredDeferredBrowserComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/FilteredDeferredClassComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/FilteredDeferredPackageComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/GroupedLabelsComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/Leaf.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/Modifier.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/ModifiersImageFactory.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/SensorTypeEnum.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/TreeModelManager.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/storage/LocalStorageLeaf.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/storage/LocalStorageTreeModelManager.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/storage/StorageLeaf.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/model/storage/StorageTreeModelManager.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/preferences/InspectITPreferenceInitializer.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/preferences/PreferenceException.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/preferences/PreferencesConstants.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/preferences/PreferencesUtils.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/preferences/StringToPrimitiveTransformUtil.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/preferences/page/CmrRepositoryPreferencePage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/ClassCollectionPreferenceValueProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/CmrRepositoryPreferenceValueProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/CollectionPreferenceValueProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/ColumnOrderPreferenceValueProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/EnumSetPreferenceValueProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/LastSelectedRepositoryPreferenceValueProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/MapPreferenceValueProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/PreferenceValueProviderFactory.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/property/CmrConfigurationDialog.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/property/GroupedPropertyPreferencePage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/property/IPropertyUpdateListener.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/property/PropertyPreferencePage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/property/control/AbstractPropertyControl.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/BooleanPropertyControl.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/BytePropertyControl.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/LongPropertyControl.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/PercentagePropertyControl.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/StringPropertyControl.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/provider/ICmrRepositoryAndAgentProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/provider/ICmrRepositoryProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/provider/IInputDefinitionProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/provider/ILocalStorageDataProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/provider/IStorageDataProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/CmrRepositoryChangeListener.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/CmrRepositoryDefinition.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/CmrRepositoryManager.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/RepositoryDefinition.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/StorageRepositoryDefinition.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/StorageRepositoryDefinitionProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/service/RefreshEditorsCachedDataService.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/CmrService.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/CmrServiceProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/ICmrService.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/KryoSimpleHttpInvokerRequestExecutor.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/proxy/CachingPlatformIdentInterceptor.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/proxy/InterceptorUtils.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/proxy/ServiceInterfaceDelegateInterceptor.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/proxy/ServiceMethodInterceptor.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/AbstractStorageService.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageExceptionDataAccessService.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageGlobalDataAccessService.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageHttpTimerDataAccessService.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageInvocationDataAccessService.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageServiceProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageSqlDataAccessService.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageTimerDataAccessService.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/resource/CombinedIcon.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/statushandlers/CustomStatusHandler.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/storage/InspectITStorageManager.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/storage/http/TransferDataMonitor.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/storage/http/TransferRateInputStream.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/storage/http/TransferRateOutputStream.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/AbstractStorageLabelComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/impl/BooleanStorageLabelComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/impl/DateStorageLabelComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/impl/NumberStorageLabelComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/impl/StringStorageLabelComposite.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/storage/label/edit/LabelValueEditingSupport.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/storage/listener/StorageChangeListener.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/storage/util/DataRetriever.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/storage/util/DataUploader.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/storage/util/DownloadHttpEntityWrapper.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/storage/util/MultipartEntityUtil.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/storage/util/UploadHttpEntityWrapper.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/tester/AgentTester.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/tester/CanRefreshViewTester.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/tester/CmrOnlineStatusTester.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/tester/InputDefinitionTester.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/tester/StorageStateTester.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/tester/TimerDataTester.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/util/AccessibleArrowImage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/util/ElementOccurrenceCount.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/util/ListenerList.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/util/OccurrenceFinderFactory.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/util/SelectionProviderAdapter.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/util/data/DatabaseInfoHelper.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/util/data/RegExAggregatedHttpTimerData.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/view/IRefreshableView.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/view/impl/DataExplorerView.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/view/impl/RepositoryManagerView.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/view/impl/StorageManagerView.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/view/listener/TreeViewDoubleClickListener.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/view/tree/StorageManagerTreeContentProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/view/tree/StorageManagerTreeLabelProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/view/tree/TreeContentProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/view/tree/TreeLabelProvider.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/view/tree/TreeViewerComparator.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/view/util/SelectionProviderIntermediate.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/AddCmrRepositoryWizard.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/AddStorageLabelWizard.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/CopyBufferToStorageWizard.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/CopyDataToStorageWizard.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/CreateStorageWizard.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/DownloadStorageWizard.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/EditCmrRepositoryWizard.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/ExportStorageWizard.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/ImportStorageWizard.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/ManageLabelWizard.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/StartRecordingWizard.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/UploadStorageWizard.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/page/AddStorageLabelWizardPage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/page/DefineCmrWizardPage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/page/DefineDataProcessorsWizardPage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/page/DefineNewStorageWizzardPage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/page/DefineTimelineWizardPage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/page/ExportStorageWizardPage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/page/ImportStorageInfoPage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/page/ImportStorageSelectPage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/page/ManageLabelWizardPage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/page/NewOrExistsingStorageWizardPage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/page/PreviewCmrDataWizardPage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/page/SelectAgentsWizardPage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/page/SelectExistingStorageWizardPage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/page/StorageCompressionWizardPage.java create mode 100644 inspectIT/src/info/novatec/inspectit/rcp/wizard/page/UploadStorageWizardPage.java create mode 100644 inspectIT/test/info/novatec/inspectit/rcp/editor/preferences/control/samplingrate/test/TimeframeDividerTest.java create mode 100644 inspectIT/test/info/novatec/inspectit/rcp/editor/search/factory/SearchFactoryTest.java create mode 100644 inspectITJMeter/.checkstyle create mode 100644 inspectITJMeter/.classpath create mode 100644 inspectITJMeter/.gitignore create mode 100644 inspectITJMeter/.pmd create mode 100644 inspectITJMeter/.project create mode 100644 inspectITJMeter/.settings/org.apache.ivyde.eclipse.prefs create mode 100644 inspectITJMeter/.settings/org.eclipse.jdt.core.prefs create mode 100644 inspectITJMeter/build.properties create mode 100644 inspectITJMeter/build.xml create mode 100644 inspectITJMeter/overwrite/config/logback.xml create mode 100644 inspectITJMeter/overwrite/info/novatec/inspectit/rcp/InspectIT.java create mode 100644 inspectITJMeter/overwrite/org/eclipse/core/runtime/IProgressMonitor.java create mode 100644 inspectITJMeter/overwrite/org/eclipse/core/runtime/SubMonitor.java create mode 100644 inspectITJMeter/resources/ivy/ivy.xml create mode 100644 inspectITJMeter/resources/testng/testng-localtest.xml create mode 100644 inspectITJMeter/resources/testng/testng.xml create mode 100644 inspectITJMeter/src/beanRefContext.xml create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITAggregatedSQL.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITAggregatedTimer.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITExceptionResult.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITGetConnectedAgents.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITHttpAggregation.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITHttpAggregationBase.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITHttpUsecaseAggregation.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITInvocationDetails.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITInvocationOverview.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITSamplerBase.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/data/AggregatedSQLResult.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/data/AggregatedTimerResult.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/data/ConnectedAgent.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/data/ConnectedAgents.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/data/CountOnlyResult.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/data/ExceptionResult.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/data/HttpAggregatedResult.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/data/HttpUsecaseResult.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/data/InspectITResultMarker.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/data/InvocationDetailResult.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/data/InvocationOverviewResult.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/data/ResultBase.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/util/ObjectConverter.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/util/ResultService.java create mode 100644 inspectITJMeter/src/info/novatec/inspectit/jmeter/util/XStreamFactory.java create mode 100644 inspectITJMeter/test/info/novatec/inspectit/jmeter/SamplerBaseTest.java create mode 100644 inspectITJMeter/test/info/novatec/inspectit/jmeter/localexecution/LocalRunner.java create mode 100644 inspectITJMeter/test/info/novatec/inspectit/jmeter/util/ResultServiceTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..dfa604b89 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*~ +*.svn +version.log +.metadata/ diff --git a/Agent/.checkstyle b/Agent/.checkstyle new file mode 100644 index 000000000..f84e73798 --- /dev/null +++ b/Agent/.checkstyle @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Agent/.classpath b/Agent/.classpath new file mode 100644 index 000000000..c73d29b61 --- /dev/null +++ b/Agent/.classpath @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/Agent/.gitignore b/Agent/.gitignore new file mode 100644 index 000000000..c0bc9185e --- /dev/null +++ b/Agent/.gitignore @@ -0,0 +1,11 @@ +/bin +/dist +/lib +/build +java-exec.properties +/resources/ivy/install/ +/resources/java15runtime/ +version.properties +/release +/test-output +/reports diff --git a/Agent/.pmd b/Agent/.pmd new file mode 100644 index 000000000..863bff9e8 --- /dev/null +++ b/Agent/.pmd @@ -0,0 +1,7 @@ + + + true + shared/config/pmd/pmd_rules.xml + false + false + diff --git a/Agent/.project b/Agent/.project new file mode 100644 index 000000000..37b03a30c --- /dev/null +++ b/Agent/.project @@ -0,0 +1,43 @@ + + + Agent + + + + + + org.eclipse.jdt.core.javabuilder + + + + + net.sourceforge.pmd.eclipse.plugin.pmdBuilder + + + + + net.sf.eclipsecs.core.CheckstyleBuilder + + + + + + org.eclipse.jdt.core.javanature + net.sourceforge.pmd.eclipse.plugin.pmdNature + net.sf.eclipsecs.core.CheckstyleNature + org.apache.ivyde.eclipse.ivynature + + + + shared + 2 + shared + + + + + shared + $%7BPARENT-1-PROJECT_LOC%7D/Commons/resources/shared + + + diff --git a/Agent/.settings/org.apache.ivyde.eclipse.prefs b/Agent/.settings/org.apache.ivyde.eclipse.prefs new file mode 100644 index 000000000..6ef82e919 --- /dev/null +++ b/Agent/.settings/org.apache.ivyde.eclipse.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.apache.ivyde.eclipse.standaloneretrieve= diff --git a/Agent/.settings/org.eclipse.jdt.core.prefs b/Agent/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..10b5e201c --- /dev/null +++ b/Agent/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,14 @@ +#Mon Jun 20 09:15:30 CEST 2011 +eclipse.preferences.version=1 +instance/org.eclipse.core.net/org.eclipse.core.net.hasMigrated=true +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.5 +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore diff --git a/Agent/META-INF/MANIFEST.MF b/Agent/META-INF/MANIFEST.MF new file mode 100644 index 000000000..84a587244 --- /dev/null +++ b/Agent/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Premain-Class: info.novatec.inspectit.agent.javaagent.JavaAgent +Can-Redefine-Classes: true \ No newline at end of file diff --git a/Agent/build.properties b/Agent/build.properties new file mode 100644 index 000000000..14d2a0972 --- /dev/null +++ b/Agent/build.properties @@ -0,0 +1,46 @@ +dist.jar.name=inspectit-agent.jar +release.name.sun15=inspectit-agent-sun1.5.zip +module.version.target=0.1 + +## Ivy +ivy.file = ${basedir}/resources/ivy/ivy.xml + +## Settings for TestNG +testng.file=${basedir}/resources/testng/testng.xml + +## Some general settings +build.common-targets.file=${commons.basedir}/resources/shared/build/common-targets.xml +src.root=${basedir}/src +test.root=${basedir}/test +config.root=${basedir}/config +ant.lib.dir=${user.home}/.ant/lib + +build.root=${basedir}/build +build.classes.root=${build.root}/classes +build.release.root=${build.root}/release +build.qa.root=${build.root}/QA +build.qa.test=${build.qa.root}/functional_tests +build.qa.test.testdata=${build.qa.test}/testdata +build.qa.test.coveragedata=${build.qa.test}/coveragedata +build.qa.analysis=${build.qa.root}/static_analysis +build.qa.analysis.pmd=${build.qa.analysis}/pmd +build.qa.analysis.findbugs=${build.qa.analysis}/findbugs +build.qa.analysis.checkstyle=${build.qa.analysis}/checkstyle +build.qa.analysis.cpd=${build.qa.analysis}/cpd + +build.agent.classes=${build.classes.root}/agent +build.instrumented.classes=${build.classes.root}/agentInstrumented +build.test.classes=${build.classes.root}/agenttest + +build.release=${build.root}/release +release.root=${basedir}/release + +## Setting for Commons +commons.basedir=${basedir}/../Commons +commons.src=${commons.basedir}/src +commons.build.release=${commons.basedir}/build/release +build.commons.classes=${commons.basedir}/build/classes/commons/classes +build.commons.file=${commons.basedir}/resources/build.xml +ivy.file.commons=${commons.basedir}/resources/ivy/ivy.xml + +java15runtime.path=${basedir}/resources/java15runtime \ No newline at end of file diff --git a/Agent/build.xml b/Agent/build.xml new file mode 100644 index 000000000..84bbc0444 --- /dev/null +++ b/Agent/build.xml @@ -0,0 +1,261 @@ + + + + + Build file for the agent component. This build provides build code for the + agent project (contains all features that the agent release shares for java + version 5 and upward). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Agent/config/common/ejb.cfg b/Agent/config/common/ejb.cfg new file mode 100644 index 000000000..e1b6a10fb --- /dev/null +++ b/Agent/config/common/ejb.cfg @@ -0,0 +1,18 @@ +################################################################ +# INFO: This instrumentation is maybe instrumenting too # +# many classes. Please check if it corresponds to your needs. # +################################################################ + +# Configuration for EJB < 3 + +sensor timer javax.ejb.EntityBean * interface=true modifiers=pub +sensor timer javax.ejb.SessionBean * interface=true modifiers=pub +sensor timer javax.ejb.MessageDrivenBean * interface=true modifiers=pub + +sensor timer javax.ejb.EJBLocalObject * interface=true modifiers=pub +sensor timer javax.ejb.EJBObject * interface=true modifiers=pub + +# Configuration for EJB 3 + +sensor timer * * @javax.ejb.Stateless modifiers=pub +sensor timer * * @javax.ejb.Stateful modifiers=pub diff --git a/Agent/config/common/exclude-classes.cfg b/Agent/config/common/exclude-classes.cfg new file mode 100644 index 000000000..0942b49f2 --- /dev/null +++ b/Agent/config/common/exclude-classes.cfg @@ -0,0 +1,25 @@ +## Exclude classes definition +############################# +## Only change the already specified patterns if you are a expert level user +## Add additional classes if needed +############################################################################ + +# This exclude is essential to remove the classes of inspectIT itself from instrumentation as this can lead +# to cyclic references. +exclude-class info.novatec.inspectit.* +exclude-class *$Proxy* +exclude-class sun.* +exclude-class java.lang.ThreadLocal +exclude-class java.lang.ref.Reference +exclude-class *_WLStub +exclude-class *[] + +# Exclude CGLIB generated classes (see #INSPECTIT-1182) +# CGLIB creates very special bytecode structures that often leads to problems with bytecode modification frameworks +# in addition the generated classes are usually not interesting for monitoring +# Workaround: If you want to monitor these classes nonetheless you can try starting your JVM with the option -Xverify:none to +# suppress any warning regarding potentially invalid bytecode +exclude-class *CGLIB$$* + +# Java8 lambda classes are not supported at the moment (see #INSPECTIT-1207) +exclude class java.lang.invoke.LambdaForm* diff --git a/Agent/config/common/hibernate.cfg b/Agent/config/common/hibernate.cfg new file mode 100644 index 000000000..dbbfa431b --- /dev/null +++ b/Agent/config/common/hibernate.cfg @@ -0,0 +1,20 @@ +################################################################ +# INFO: This instrumentation is maybe instrumenting too # +# many classes. Please check if it corresponds to your needs. # +################################################################ + +sensor timer org.hibernate.impl.QueryImpl * modifiers=pub + +sensor timer org.hibernate.impl.SessionImpl load modifiers=pub +sensor timer org.hibernate.impl.SessionImpl get modifiers=pub +sensor timer org.hibernate.impl.SessionImpl evict modifiers=pub +sensor timer org.hibernate.impl.SessionImpl flush modifiers=pub +sensor timer org.hibernate.impl.SessionImpl forceFlush modifiers=pub +sensor timer org.hibernate.impl.SessionImpl find modifiers=pub +sensor timer org.hibernate.impl.SessionImpl list modifiers=pub +sensor timer org.hibernate.impl.SessionImpl iterate modifiers=pub +sensor timer org.hibernate.impl.SessionImpl delete modifiers=pub +sensor timer org.hibernate.impl.SessionImpl executeUpdate modifiers=pub +sensor timer org.hibernate.impl.SessionImpl executeNativeUpdate modifiers=pub + +sensor timer org.hibernate.impl.SessionFactoryImpl openSession \ No newline at end of file diff --git a/Agent/config/common/http.cfg b/Agent/config/common/http.cfg new file mode 100644 index 000000000..a1e993d5e --- /dev/null +++ b/Agent/config/common/http.cfg @@ -0,0 +1,21 @@ +################################################################ +# INFO: This instrumentation is maybe instrumenting too # +# many classes. Please check if it corresponds to your needs. # +################################################################ + +# Definition of the sensor type. We do that here as we only will need it when activating +# the http sensor. +# You can add the capturing of session attributes by adding "sessioncapture=true" (without +# the quotations to the end of this definition. +method-sensor-type http info.novatec.inspectit.agent.sensor.method.http.HttpSensor MAX stringLength=500 + +# The sensor can specify the regular expression that can be performed on the URI +# Additionally the template can be specified to provide better looking results, where $1$, $2$, $3$, etc are substituted with the groups found in regular expression +# The following example take first and second URI component parts and creates the template with them +# method-sensor-type http info.novatec.inspectit.agent.sensor.method.http.HttpSensor MAX stringLength=500 regEx=/([^"]+)/([^"]+) regExTemplate=App:$1$,Action:$2$ + +sensor isequence javax.servlet.Filter doFilter(javax.servlet.ServletRequest,javax.servlet.ServletResponse,javax.servlet.FilterChain) interface=true +sensor isequence javax.servlet.Servlet service(javax.servlet.ServletRequest,javax.servlet.ServletResponse) interface=true + +sensor http javax.servlet.Filter doFilter(javax.servlet.ServletRequest,javax.servlet.ServletResponse,javax.servlet.FilterChain) interface=true charting=true +sensor http javax.servlet.Servlet service(javax.servlet.ServletRequest,javax.servlet.ServletResponse) interface=true charting=true diff --git a/Agent/config/common/jpa.cfg b/Agent/config/common/jpa.cfg new file mode 100644 index 000000000..6ddfaa131 --- /dev/null +++ b/Agent/config/common/jpa.cfg @@ -0,0 +1,10 @@ +################################################################ +# INFO: This instrumentation is maybe instrumenting too # +# many classes. Please check if it corresponds to your needs. # +################################################################ + +sensor timer javax.persistence.EntityManager * interface=true modifiers=pub +sensor timer javax.persistence.EntityManagerFactory * interface=true modifiers=pub + +sensor timer javax.persistence.EntityTransaction * interface=true modifiers=pub +sensor timer javax.persistence.Query * interface=true modifiers=pub \ No newline at end of file diff --git a/Agent/config/common/jsf.cfg b/Agent/config/common/jsf.cfg new file mode 100644 index 000000000..9d9666005 --- /dev/null +++ b/Agent/config/common/jsf.cfg @@ -0,0 +1,27 @@ +################################################################ +# INFO: This instrumentation is maybe instrumenting too # +# many classes. Please check if it corresponds to your needs. # +################################################################ + +sensor timer javax.faces.webapp.UIComponentClassicTagBase * modifiers=pub + +sensor timer javax.faces.component.* processDecodes modifiers=pub +sensor timer javax.faces.component.* processRestoreState modifiers=pub +sensor timer javax.faces.component.* processSaveState modifiers=pub +sensor timer javax.faces.component.* processUpdates modifiers=pub +sensor timer javax.faces.component.* processValidators modifiers=pub +sensor timer javax.faces.component.* saveState modifiers=pub +sensor timer javax.faces.component.* restoreState modifiers=pub + +sensor timer org.apache.myfaces.trinidad.render.CoreRenderer * modifiers=pub + +sensor timer org.apache.myfaces.trinidad.component.* saveState modifiers=pub +sensor timer org.apache.myfaces.trinidad.component.* encodeBegin modifiers=pub +sensor timer org.apache.myfaces.trinidad.component.* encodeEnd modifiers=pub +sensor timer org.apache.myfaces.trinidad.component.* encodeAll modifiers=pub +sensor timer org.apache.myfaces.trinidad.component.* processSaveState modifiers=pub +sensor timer org.apache.myfaces.trinidad.component.* processDecodes modifiers=pub +sensor timer org.apache.myfaces.trinidad.component.* processValidators modifiers=pub +sensor timer org.apache.myfaces.trinidad.component.* processUpdates modifiers=pub +sensor timer org.apache.myfaces.trinidad.component.* processRestoreState modifiers=pub +sensor timer org.apache.myfaces.trinidad.component.* restoreState modifiers=pub diff --git a/Agent/config/common/jta.cfg b/Agent/config/common/jta.cfg new file mode 100644 index 000000000..e6cfedb04 --- /dev/null +++ b/Agent/config/common/jta.cfg @@ -0,0 +1,19 @@ +################################################################ +# INFO: This instrumentation is maybe instrumenting too # +# many classes. Please check if it corresponds to your needs. # +################################################################ + +sensor timer javax.transaction.UserTransaction begin interface=true modifiers=pub +sensor timer javax.transaction.UserTransaction commit interface=true modifiers=pub +sensor timer javax.transaction.UserTransaction getStatus interface=true modifiers=pub +sensor timer javax.transaction.UserTransaction rollback interface=true modifiers=pub +sensor timer javax.transaction.UserTransaction setRollbackOnly interface=true modifiers=pub +sensor timer javax.transaction.UserTransaction setTransactionTimeout(int) interface=true modifiers=pub + +sensor timer javax.transaction.TransactionManager begin interface=true modifiers=pub +sensor timer javax.transaction.TransactionManager commit interface=true modifiers=pub +sensor timer javax.transaction.TransactionManager rollback interface=true modifiers=pub +sensor timer javax.transaction.TransactionManager resume interface=true modifiers=pub +sensor timer javax.transaction.TransactionManager suspend interface=true modifiers=pub +#sensor timer javax.transaction.TransactionManager getStatus interface=true modifiers=pub +#sensor timer javax.transaction.TransactionManager getTransaction interface=true modifiers=pub diff --git a/Agent/config/common/sql-parameters.cfg b/Agent/config/common/sql-parameters.cfg new file mode 100644 index 000000000..08d45805c --- /dev/null +++ b/Agent/config/common/sql-parameters.cfg @@ -0,0 +1,89 @@ +## SQL Parameter +####################### +# SQL Prepared Statement Parameter Replacement +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setArray(int,java.sql.Array) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setAsciiStream(int,java.io.InputStream) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setAsciiStream(int,java.io.InputStream,int) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setAsciiStream(int,java.io.InputStream,long) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBigDecimal(int,java.math.BigDecimal) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBinaryStream(int,java.io.InputStream) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBinaryStream(int,java.io.InputStream,int) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBinaryStream(int,java.io.InputStream,long) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBlob(int,java.sql.Blob) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBlob(int,java.io.InputStream) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBlob(int,java.io.InputStream,long) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBoolean(int,boolean) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setByte(int,byte) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBytes(int,byte[]) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setCharacterStream(int,java.io.Reader) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setCharacterStream(int,java.io.Reader,int) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setCharacterStream(int,java.io.Reader,long) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setClob(int,java.sql.Clob) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setClob(int,java.io.Reader) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setClob(int,java.io.Reader,long) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setDate(int,java.sql.Date) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setDate(int,java.sql.Date,java.util.Calendar) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setDouble(int,double) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setFloat(int,float) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setInt(int,int) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setLong(int,long) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNCharacterStream(int,java.io.Reader) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNCharacterStream(int,java.io.Reader,long) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNClob(int,java.sql.NClob) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNClob(int,java.io.Reader) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNClob(int,java.io.Reader,long) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNClob(int,java.sql.NClob) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNString(int,java.lang.String) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setObject(int,java.lang.Object) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setObject(int,java.lang.Object,int) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setObject(int,java.lang.Object,int,int) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setRef(int,java.sql.Ref) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setRowId(int,java.sql.RowId) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setShort(int,short) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setSQLXML(int,java.sql.SQLXML) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setString(int,java.lang.String) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setTime(int,java.sql.Time) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setTime(int,java.sql.Time,java.util.Calendar) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setTimestamp(int,java.sql.Timestamp) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setTimestamp(int,java.sql.Timestamp,java.util.Calendar) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setUnicodeStream(int,java.io.InputStream,int) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setURL(int,java.net.URL) interface=true + +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNull(int,int) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNull(int,int,java.lang.String) interface=true + +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement clearParameters() interface=true + +# Postgre SQL Prepared Statement Parameter Replacement +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setArray(int,java.sql.Array) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setAsciiStream(int,java.io.InputStream,int) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setBigDecimal(int,java.math.BigDecimal) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setBinaryStream(int,java.io.InputStream,int) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setBlob(int,java.sql.Blob) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setBoolean(int,boolean) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setByte(int,byte) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setBytes(int,byte[]) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setCharacterStream(int,java.io.Reader,int) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setClob(int,java.sql.Clob) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setDate(int,java.sql.Date) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setDate(int,java.sql.Date,java.util.Calendar) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setDouble(int,double) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setFloat(int,float) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setInt(int,int) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setLong(int,long) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setObject(int,java.lang.Object) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setObject(int,java.lang.Object,int) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setObject(int,java.lang.Object,int,int) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setRef(int,java.sql.Ref) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setShort(int,short) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setString(int,java.lang.String) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setTime(int,java.sql.Time) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setTime(int,java.sql.Time,java.util.Calendar) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setTimestamp(int,java.sql.Timestamp) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setTimestamp(int,java.sql.Timestamp,java.util.Calendar) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setUnicodeStream(int,java.io.InputStream,int) + +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setNull(int,int) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setNull(int,int,java.lang.String) + +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement clearParameters() \ No newline at end of file diff --git a/Agent/config/common/sql.cfg b/Agent/config/common/sql.cfg new file mode 100644 index 000000000..477a0857f --- /dev/null +++ b/Agent/config/common/sql.cfg @@ -0,0 +1,58 @@ +## SQL Tracing (generic) +######################## +# SQL Connection +sensor jdbc-connection java.sql.Connection prepareStatement(java.lang.String) interface=true +sensor jdbc-connection java.sql.Connection prepareStatement(java.lang.String,int) interface=true +sensor jdbc-connection java.sql.Connection prepareStatement(java.lang.String,int[]) interface=true +sensor jdbc-connection java.sql.Connection prepareStatement(java.lang.String,int,int) interface=true +sensor jdbc-connection java.sql.Connection prepareStatement(java.lang.String,int,int,int) interface=true +sensor jdbc-connection java.sql.Connection prepareStatement(java.lang.String,java.lang.String[]) interface=true +sensor jdbc-connection java.sql.Connection prepareCall(java.lang.String) interface=true +sensor jdbc-connection java.sql.Connection prepareCall(java.lang.String,int,int) interface=true +sensor jdbc-connection java.sql.Connection prepareCall(java.lang.String,int,int,int) interface=true +# WebLogic SQL Connection +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareStatement(java.lang.String) interface=true +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareStatement(java.lang.String,int) interface=true +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareStatement(java.lang.String,int[]) interface=true +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareStatement(java.lang.String,int,int) interface=true +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareStatement(java.lang.String,int,int,int) interface=true +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareStatement(java.lang.String,java.lang.String[]) interface=true +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareCall(java.lang.String) interface=true +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareCall(java.lang.String,int,int) interface=true +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareCall(java.lang.String,int,int,int) interface=true + +# Connection Meta Data +sensor jdbc-connection-meta-data java.sql.Connection interface=true +sensor jdbc-connection-meta-data weblogic.jdbc.wrapper.Connection interface=true + +# SQL Prepared Statement +sensor jdbc-prepared-statement java.sql.PreparedStatement interface=true +sensor jdbc-prepared-statement java.sql.PreparedStatement executeQuery() interface=true +sensor jdbc-prepared-statement java.sql.PreparedStatement executeUpdate() interface=true +sensor jdbc-prepared-statement java.sql.PreparedStatement execute() interface=true +sensor jdbc-prepared-statement java.sql.Statement executeBatch() interface=true +# PostgreSQL Prepared Statement +sensor jdbc-prepared-statement org.postgresql.jdbc2.AbstractJdbc2Statement executeQuery() +sensor jdbc-prepared-statement org.postgresql.jdbc2.AbstractJdbc2Statement executeUpdate() +sensor jdbc-prepared-statement org.postgresql.jdbc2.AbstractJdbc2Statement execute() +# SQL Statement +sensor jdbc-statement java.sql.Statement execute(java.lang.String) interface=true +sensor jdbc-statement java.sql.Statement execute(java.lang.String,int) interface=true +sensor jdbc-statement java.sql.Statement execute(java.lang.String,int[]) interface=true +sensor jdbc-statement java.sql.Statement execute(java.lang.String,java.lang.String[]) interface=true +sensor jdbc-statement java.sql.Statement executeUpdate(java.lang.String)interface=true +sensor jdbc-statement java.sql.Statement executeUpdate(java.lang.String,int) interface=true +sensor jdbc-statement java.sql.Statement executeUpdate(java.lang.String,int[]) interface=true +sensor jdbc-statement java.sql.Statement executeUpdate(java.lang.String,java.lang.String[]) interface=true +sensor jdbc-statement java.sql.Statement executeQuery(java.lang.String) interface=true +# H2 +sensor jdbc-connection org.h2.jdbc.JdbcConnection prepareAutoCloseStatement(java.lang.String) +# WebSphere / DB2 +sensor jdbc-connection com.ibm.db2.jcc.t4.b c(java.lang.String,int,int,int) +sensor jdbc-connection com.ibm.db2.jcc.t4.* newCallableStatement_(java.lang.String,int,int,int) +sensor jdbc-connection com.ibm.icm.da.portable.common.sql.DefaultPConnection prepareStatement1(java.lang.String) +# Derby metadata queries +sensor jdbc-connection org.apache.derby.impl.jdbc.EmbedConnection prepareMetaDataStatement(java.lang.String) + +# Exclude classes that are not meaningful +exclude-class oracle.jdbc.driver.OracleClosedStatement diff --git a/Agent/config/common/struts.cfg b/Agent/config/common/struts.cfg new file mode 100644 index 000000000..f40cbd7b4 --- /dev/null +++ b/Agent/config/common/struts.cfg @@ -0,0 +1,12 @@ +################################################################ +# INFO: This instrumentation is maybe instrumenting too # +# many classes. Please check if it corresponds to your needs. # +################################################################ + +sensor timer org.apache.struts.action.* * modifiers=pub +sensor timer org.apache.struts.config.* * modifiers=pub +sensor timer org.apache.struts.taglib.html.* * modifiers=pub +sensor timer org.apache.struts.taglib.tiles.* * modifiers=pub +sensor timer org.apache.struts.tiles.* * modifiers=pub +sensor timer org.apache.struts.upload.* * modifiers=pub +sensor timer org.apache.struts.util.* * modifiers=pub \ No newline at end of file diff --git a/Agent/config/common/webservice.cfg b/Agent/config/common/webservice.cfg new file mode 100644 index 000000000..e2d70237e --- /dev/null +++ b/Agent/config/common/webservice.cfg @@ -0,0 +1,7 @@ +################################################################ +# INFO: This instrumentation is maybe instrumenting too # +# many classes. Please check if it corresponds to your needs. # +################################################################ + +sensor timer * * @javax.jws.WebService modifiers=pub +sensor timer * * @javax.ws.rs.Path modifiers=pub diff --git a/Agent/config/inspectit-agent.cfg b/Agent/config/inspectit-agent.cfg new file mode 100644 index 000000000..82a7cf21b --- /dev/null +++ b/Agent/config/inspectit-agent.cfg @@ -0,0 +1,113 @@ +## repository +############################################# +repository localhost 9070 inspectIT + +## method-sensor-type [] +##################################################################################### +# method-sensor-type average-timer info.novatec.inspectit.agent.sensor.method.averagetimer.AverageTimerSensor HIGH stringLength=100 +method-sensor-type timer info.novatec.inspectit.agent.sensor.method.timer.TimerSensor MAX stringLength=100 +method-sensor-type isequence info.novatec.inspectit.agent.sensor.method.invocationsequence.InvocationSequenceSensor INVOC stringLength=100 +method-sensor-type jdbc-connection info.novatec.inspectit.agent.sensor.method.jdbc.ConnectionSensor MIN +method-sensor-type jdbc-prepared-statement info.novatec.inspectit.agent.sensor.method.jdbc.PreparedStatementSensor MIN stringLength=1000 +method-sensor-type jdbc-prepared-statement-parameter info.novatec.inspectit.agent.sensor.method.jdbc.PreparedStatementParameterSensor MIN +method-sensor-type jdbc-statement info.novatec.inspectit.agent.sensor.method.jdbc.StatementSensor MIN stringLength=1000 +method-sensor-type jdbc-connection-meta-data info.novatec.inspectit.agent.sensor.method.jdbc.ConnectionMetaDataSensor MIN + +## to activate jRebel support add jRebel=true to sensor definition +## method-sensor-type timer info.novatec.inspectit.agent.sensor.method.timer.TimerSensor MAX jRebel=true + +## exception-sensor-type [] +###################################################################### +exception-sensor-type info.novatec.inspectit.agent.sensor.exception.ExceptionSensor mode=simple stringLength=500 + +## platform-sensor-type [] +##################################################################### +platform-sensor-type info.novatec.inspectit.agent.sensor.platform.ClassLoadingInformation +platform-sensor-type info.novatec.inspectit.agent.sensor.platform.CompilationInformation +platform-sensor-type info.novatec.inspectit.agent.sensor.platform.MemoryInformation +platform-sensor-type info.novatec.inspectit.agent.sensor.platform.CpuInformation +platform-sensor-type info.novatec.inspectit.agent.sensor.platform.RuntimeInformation +platform-sensor-type info.novatec.inspectit.agent.sensor.platform.SystemInformation +platform-sensor-type info.novatec.inspectit.agent.sensor.platform.ThreadInformation + +## send-strategy +####################################### +send-strategy info.novatec.inspectit.agent.sending.impl.TimeStrategy time=5000 +# send-strategy info.novatec.inspectit.agent.sending.impl.ListSizeStrategy size=10 + +## buffer-strategy +######################################### +buffer-strategy info.novatec.inspectit.agent.buffer.impl.SimpleBufferStrategy +#buffer-strategy info.novatec.inspectit.agent.buffer.impl.SizeBufferStrategy size=12 + +## Ignore classes settings +######################################### +$include common/exclude-classes.cfg + +## SQL tracing +############## +$include common/sql.cfg +## Uncomment configuration file under to enable parameter binding for SQL queries. This feature allows to have +## prepared Statements filled with the concrete parameter value that it was executed with, instead of just "?" values. +## Bear in mind that activating this feature will affect performance in a negative way as more methods need to be instrumented. +# $include common/sql-parameters.cfg + +## Common technologies (please uncomment wanted) +################################################ +# $include common/ejb.cfg +# $include common/http.cfg +# $include common/hibernate.cfg +# $include common/struts.cfg +# $include common/jsf.cfg +# $include common/jpa.cfg +# $include common/jta.cfg +# $include common/webservice.cfg + + +## EXAMPLES +########### +# sensor timer novatec.SubTest msg(java.lang.String) +# sensor timer novatec.Sub* msg(int) modifiers=pub +# sensor timer novatec.SubTest msg(*String) modifiers=pub +# sensor timer novatec.Sub* m*(java.lang.String,*String) modifiers=pub +# sensor timer novatec.ITest * interface=true modifiers=pub,prot +# sensor timer nova*.Te* m*(*String) superclass=true modifiers=pub,prot +# sensor timer * * @javax.ejb.Stateless modifiers=pub + +# sensor timer info.novatec.inspectitsamples.calculator.Calculator * modifiers=pub +# sensor timer info.novatec.inspectitsamples.calculator.Calculator substract(double,double) +# sensor timer info.novatec.inspectitsamples.calculator.Calculator divide(double,double) +# sensor timer info.novatec.inspectitsamples.calculator.Calculator multiply(double,double) + +## The following aren't working properly (for java classes), just added to show the usage of the superclass/interface option +# sensor timer java.lang.Object * superclass=true modifiers=pub +# sensor timer java.util.List size() interface=true modifiers=pub + +## For parameter catching: p=parameter of the method | f=field of the class/instance | r=return value of a method +# sensor isequence info.novatec.inspectitsamples.calculator.Calculator actionPerformed p=0;Source; f=LastOperator;lastOperator f=LastOutput;jlbOutput.text + +# reads the result of the substract method (which is a double) +# sensor timer info.novatec.inspectitsamples.calculator.Calculator substract(double,double) r=substractionResult; + +## For constructors: +# sensor timer info.novatec.inspectitsamples.calculator.Calculator modifiers=pub + +# Specified modifiers can be: pub (public), prot (protected), priv (private) and def (default) + +# To save timer data to DB for charting and future checking use charting=true +# sensor timer novatec.SubTest msg(java.lang.String) charting=true + +## Exception catching +# exception-sensor info.novatec.exception.MyException +# exception-sensor info.novatec.ex*.*Exception + +## You can include additional sensor configuration files (e.g. for separation or easier (de-)activation) like this +## If no absolute path is specified, the folder containing this file will be taken as the root! +# $include sensors.cfg +# $include api1.cfg +# $include api2.cfg +# $include /my/path/to/the/configuration/ui-sensors.cfg + +## Your Sensor Definitions +########################## +# sensor [] diff --git a/Agent/config/logging.properties b/Agent/config/logging.properties new file mode 100644 index 000000000..9cf6de206 --- /dev/null +++ b/Agent/config/logging.properties @@ -0,0 +1,17 @@ +# This is the default logging configuration for the inspectIT agent. Feel free +# to adapt this logging to your needs. +# +# To integrate this logging configuration set the "-Djava.util.logging.config.file" Parameter to +# point to the configuration (for example: -Djava.util.logging.config.file=[path-to]/logging.properties + + +# The console handler logs output to the console +handlers= java.util.logging.ConsoleHandler + +# In addition to that additional handlers to store the information to a file can be added +#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler + +# Default log level - Change this to "fine" for more information or to "severe" to only get errors +.level= INFO + +java.util.logging.ConsoleHandler.formatter = info.novatec.inspectit.util.MessageFormatFormatter \ No newline at end of file diff --git a/Agent/logging-config.xml b/Agent/logging-config.xml new file mode 100644 index 000000000..8fd46233e --- /dev/null +++ b/Agent/logging-config.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + System.out + + %d{ISO8601}: [inspectIT] %-6r [%15.15t] %-5p %30.30c - %m%n%nopex + + + + + + + ${logDir}/agent.log + + %d{ISO8601}: %-6r [%15.15t] %-5p %30.30c - %m%n%rEx + + + logDir/agent.%d{yyyy-MM-dd}.%i.zip + 30 + + 20MB + + + + + + + + + ${logDir}/exceptions.log + + %d{ISO8601}: %-6r [%15.15t] %-5p %30.30c - %m%n%rEx + + + ${logDir}/exceptions.%d{yyyy-MM-dd}.%i.zip + 30 + + 20MB + + + + WARN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Agent/resources/ivy/ivy.xml b/Agent/resources/ivy/ivy.xml new file mode 100644 index 000000000..d1da5a7c1 --- /dev/null +++ b/Agent/resources/ivy/ivy.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Agent/resources/testng/testng.xml b/Agent/resources/testng/testng.xml new file mode 100644 index 000000000..08f1303ab --- /dev/null +++ b/Agent/resources/testng/testng.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Agent/src/config/bytebufferpool.properties b/Agent/src/config/bytebufferpool.properties new file mode 100644 index 000000000..7fbe113e0 --- /dev/null +++ b/Agent/src/config/bytebufferpool.properties @@ -0,0 +1,17 @@ +# size of the buffers that will be used for serialization - 1MB +storage.bufferSize = 1048576 + +# amount of bytes that is min available in byte buffer pool - 30MB +# note that this will be spread in several buffers depending on the size of each buffer +# if this size is greater than the percentage of direct memory specified by bufferPoolMinDirectMemoryOccupancy, it will be decreased to that percentage +storage.bufferPoolMinCapacity = 31457280 + +# max amount of bytes that will be created in byte buffer pool - 150MB +# if this size is greater than the percentage of direct memory specified by bufferPoolMaxDirectMemoryOccupancy, it will be decreased to that percentage +storage.bufferPoolMaxCapacity = 157286400 + +# byte buffer pool min size based on the percentage of the direct memory available to the JVM - 30% +storage.bufferPoolMinDirectMemoryOccupancy = 0.3 + +# byte buffer pool max size based on the percentage of the direct memory available to the JVM - 60% +storage.bufferPoolMaxDirectMemoryOccupancy = 0.6 diff --git a/Agent/src/info/novatec/inspectit/agent/Agent.java b/Agent/src/info/novatec/inspectit/agent/Agent.java new file mode 100644 index 000000000..3c5386f74 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/Agent.java @@ -0,0 +1,26 @@ +package info.novatec.inspectit.agent; + +/** + * Another agent which is there for the sole purpose of classloading issues. The JavaAgent class can + * be loaded via the AppClassLoader (and all of the classes of the fields and method signatures + * etc.). Thus we need another class to be loaded in the bootstrap classloader. And as there is no + * possibility in getting the bootstrap classloader, we need another class not defined nearly + * anywhere in the JavaAgent class and loaded after the initialization is complete. Tada. + * + * @author Patrice Bouillet + * + */ +public final class Agent { + + /** + * The real agent implementation to point to. + */ + public static IAgent agent; // NOPMD NOCHK + + /** + * Private constructor to prevent instantiation. + */ + private Agent() { + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/IAgent.java b/Agent/src/info/novatec/inspectit/agent/IAgent.java new file mode 100644 index 000000000..f0af1340b --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/IAgent.java @@ -0,0 +1,50 @@ +package info.novatec.inspectit.agent; + +import info.novatec.inspectit.agent.hooking.IHookDispatcher; + +/** + * Interface for accessing the real agent implementation from the SUD and our JavaAgent. + * + * @author Patrice Bouillet + * + */ +public interface IAgent { + + /** + * This method will inspect the given byte code and class name to check if it needs to be + * instrumented by the Agent. The class loader is needed as different versions of the same class + * can be loaded. + * + * @param byteCode + * The byte code. + * @param className + * The name of the class + * @param classLoader + * The class loader of the passed class. + * @return Returns the instrumented byte code if something has been changed, otherwise + * null. + */ + byte[] inspectByteCode(byte[] byteCode, String className, ClassLoader classLoader); + + /** + * Returns the hook dispatcher. This is needed for the instrumented methods in the target + * application! Otherwise the entry point for them would be missing. + * + * @return The hook dispatcher + */ + IHookDispatcher getHookDispatcher(); + + /** + * Loads class with the given parameters that have been passed to the target class loader. + *

+ * Loading will be delegated only if parameters are of size 1 and that single parameter is + * String type. + * + * @see info.novatec.inspectit.agent.PicoAgent#loadClass(String) + * @param params + * Original parameters passed to class loader. + * @return Loaded class or null. + */ + Class loadClass(Object[] params); + +} \ No newline at end of file diff --git a/Agent/src/info/novatec/inspectit/agent/SpringAgent.java b/Agent/src/info/novatec/inspectit/agent/SpringAgent.java new file mode 100644 index 000000000..786d36a2a --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/SpringAgent.java @@ -0,0 +1,200 @@ +package info.novatec.inspectit.agent; + +import info.novatec.inspectit.agent.analyzer.IByteCodeAnalyzer; +import info.novatec.inspectit.agent.analyzer.IMatchPattern; +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.hooking.IHookDispatcher; +import info.novatec.inspectit.agent.logback.LogInitializer; +import info.novatec.inspectit.agent.spring.SpringConfiguration; +import info.novatec.inspectit.versioning.IVersioningService; + +import java.io.IOException; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +/** + * The {@link SpringAgent} is used by the javaagent to analyze the passed bytecode if its needed to + * be instrumented. The {@link #getInstance()} method returns the singleton instance of this class. + *

+ * The {@link #inspectByteCode(byte[], String, ClassLoader)} is the method which should be called by + * the javaagent. The method returns null if nothing has to be changed or something happened + * unexpectedly. + *

+ * This class is named SpringAgent as its using the Spring to handle the different components + * in the Agent. + * + * @author Patrice Bouillet + * + */ +public class SpringAgent implements IAgent { + + /** + * The logger of this class. Initialized manually. + */ + private static final Logger LOG = LoggerFactory.getLogger(SpringAgent.class); + + /** + * Our class start with {@value #CLASS_NAME_PREFIX}. + */ + private static final String CLASS_NAME_PREFIX = "info.novatec.inspectit"; + + /** + * The hook dispatcher used by the instrumented methods. + */ + private IHookDispatcher hookDispatcher; + + /** + * Set to true if something happened while trying to initialize the pico container. + */ + private boolean initializationError = false; + + /** + * Created bean factory. + */ + private BeanFactory beanFactory; + + /** + * Constructor initializing this agent. + * + * @param inspectitJarLocation + * location of inspectIT jar needed for proper logging + */ + public SpringAgent(String inspectitJarLocation) { + LogInitializer.setInspectitJarLocation(inspectitJarLocation); + LogInitializer.initLogging(); + this.initSpring(); + } + + /** + * Initializes the spring. + */ + private void initSpring() { + if (LOG.isInfoEnabled()) { + LOG.info("Initializing Spring on inspectIT Agent..."); + } + + // set inspectIT class loader to be the context class loader + // so that bean factory can use correct class loader for finding the classes + ClassLoader inspectITClassLoader = this.getClass().getClassLoader(); + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(inspectITClassLoader); + + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(SpringConfiguration.class); + ctx.refresh(); + beanFactory = ctx; + + if (LOG.isInfoEnabled()) { + LOG.info("Spring successfully initialized"); + } + + // log version + if (LOG.isInfoEnabled()) { + String currentVersion = "n/a"; + try { + currentVersion = beanFactory.getBean(IVersioningService.class).getVersion(); + } catch (IOException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Version information could not be read", e); + } + } + LOG.info("Using agent version " + currentVersion); + } + + hookDispatcher = beanFactory.getBean(IHookDispatcher.class); + + // switch back to the original context class loader + Thread.currentThread().setContextClassLoader(contextClassLoader); + } + + /** + * {@inheritDoc} + */ + public byte[] inspectByteCode(byte[] byteCode, String className, ClassLoader classLoader) { + // if an error in the init method was caught, we'll do nothing here. + // This prevents further errors. + if (initializationError) { + return byteCode; + } + + // ignore all classes which fit to the patterns in the configuration + IConfigurationStorage configurationStorage = beanFactory.getBean(IConfigurationStorage.class); + List ignoreClassesPatterns = configurationStorage.getIgnoreClassesPatterns(); + for (IMatchPattern matchPattern : ignoreClassesPatterns) { + if (matchPattern.match(className)) { + return byteCode; + } + } + + IByteCodeAnalyzer byteCodeAnalyzer = beanFactory.getBean(IByteCodeAnalyzer.class); + try { + byte[] instrumentedByteCode = byteCodeAnalyzer.analyzeAndInstrument(byteCode, className, classLoader); + return instrumentedByteCode; + } catch (Throwable throwable) { // NOPMD + LOG.error("Something unexpected happened while trying to analyze or instrument the bytecode with the class name: " + className, throwable); + return byteCode; + } + } + + /** + * {@inheritDoc} + */ + public Class loadClass(Object[] params) { + try { + if (null != params && params.length == 1) { + Object p = params[0]; + if (p instanceof String) { + return loadClass((String) p); + } + } + return null; + } catch (Throwable e) { // NOPMD + return null; + } + } + + /** + * Delegates the class loading to the {@link #inspectItClassLoader} if the class name starts + * with {@value #CLASS_NAME_PREFIX}. Otherwise loads the class with the target class loader. If + * the inspectIT class loader throws {@link ClassNotFoundException}, the target class loader + * will be used. + * + * @param className + * Class name. + * @return Loaded class or null if it can not be found with inspectIT class loader. + */ + private Class loadClass(String className) { + if (loadWithInspectItClassLoader(className)) { + try { + return getClass().getClassLoader().loadClass(className); + } catch (ClassNotFoundException e) { + return null; + } + } else { + return null; + } + } + + /** + * Defines if the class should be loaded with our class loader. + * + * @param className + * Name of the class to load. + * @return True if class name starts with {@value #CLASS_NAME_PREFIX}. + */ + private boolean loadWithInspectItClassLoader(String className) { + return className.startsWith(CLASS_NAME_PREFIX); + } + + /** + * {@inheritDoc} + */ + public IHookDispatcher getHookDispatcher() { + return hookDispatcher; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/analyzer/IByteCodeAnalyzer.java b/Agent/src/info/novatec/inspectit/agent/analyzer/IByteCodeAnalyzer.java new file mode 100644 index 000000000..d0a2b662e --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/analyzer/IByteCodeAnalyzer.java @@ -0,0 +1,28 @@ +package info.novatec.inspectit.agent.analyzer; + +/** + * This interface is used to delegate the analysis and instrumentation of the given bytecode from + * the javaagent. + * + * @author Patrice Bouillet + * + */ +public interface IByteCodeAnalyzer { + + /** + * The method returns the instrumented bytecode of the class which is passed to this method as + * the first parameter. + * + * @param byteCode + * The bytecode, which is necessary to check if a parent class is registered by a + * sensor and needs to be installed for every child class. + * @param className + * The class name. + * @param classLoader + * The class loader. + * @return The instrumented byte code or null if nothing was done (or an error + * happened) + */ + byte[] analyzeAndInstrument(byte[] byteCode, String className, ClassLoader classLoader); + +} diff --git a/Agent/src/info/novatec/inspectit/agent/analyzer/IClassPoolAnalyzer.java b/Agent/src/info/novatec/inspectit/agent/analyzer/IClassPoolAnalyzer.java new file mode 100644 index 000000000..5c647cda1 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/analyzer/IClassPoolAnalyzer.java @@ -0,0 +1,55 @@ +package info.novatec.inspectit.agent.analyzer; + +import javassist.ClassPool; +import javassist.CtConstructor; +import javassist.CtMethod; + +/** + * This interface defines methods to help with the usage of the javassist class pool. + * + * @author Patrice Bouillet + * + */ +public interface IClassPoolAnalyzer { + + /** + * Returns all the methods of a class as an array of {@link CtMethod} objects. + * + * @param classLoader + * The class loader of the given class name to successfully search for the class. + * @param className + * The name of the class. + * @return The array of {@link CtMethod} objects of the passed class name. + */ + CtMethod[] getMethodsForClassName(final ClassLoader classLoader, final String className); + + /** + * Returns all the constructors of a class as an array of {@link CtConstructor} objects. + * + * @param classLoader + * The class loader of the given class name to successfully search for the class. + * @param className + * The name of the class. + * @return The array of {@link CtConstructor} objects of the passed class name. + */ + CtConstructor[] getConstructorsForClassName(final ClassLoader classLoader, final String className); + + /** + * Copy the hierarchy from the given classloader and build new classpool objects. + * + * @param classLoader + * The class loader. + * @return The ClassPool referring to this class loader. + */ + ClassPool addClassLoader(final ClassLoader classLoader); + + /** + * Returns the {@link ClassPool} which is responsible for the given class loader. + * + * @param classLoader + * The class loader. + * @return The ClassPool referring to this class loader. + */ + ClassPool getClassPool(final ClassLoader classLoader); + +} \ No newline at end of file diff --git a/Agent/src/info/novatec/inspectit/agent/analyzer/IInheritanceAnalyzer.java b/Agent/src/info/novatec/inspectit/agent/analyzer/IInheritanceAnalyzer.java new file mode 100644 index 000000000..f104261a8 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/analyzer/IInheritanceAnalyzer.java @@ -0,0 +1,83 @@ +package info.novatec.inspectit.agent.analyzer; + +import java.util.Iterator; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; + +/** + * The inheritance analyzer is a used to identify classes which have to be instrumented by inspectIT + * but aren't directly specified in the configuration file but through their superclass. + * + * @author Patrice Bouillet + * + */ +public interface IInheritanceAnalyzer { + + /** + * Returns an iterator over all superclasses of a class. + * + * @param classLoader + * The class loader of the class. + * @param className + * The name of the class. + * @return An {@link Iterator} of all superclasses. + * @throws NotFoundException + * This exception is thrown if a class is not found from within Javassist. + */ + Iterator getSuperclassIterator(ClassLoader classLoader, String className) throws NotFoundException; + + /** + * Returns an iterator over all implemented interfaces of a class. + * + * @param classLoader + * The class loader of the class. + * @param className + * The name of the class. + * @return An {@link Iterator} of all interfaces. + * @throws NotFoundException + * This exception is thrown if a class is not found from within Javassist. + */ + Iterator getInterfaceIterator(ClassLoader classLoader, String className) throws NotFoundException; + + /** + * Returns true if the class name defines an interface. + * + * @param className + * The name of the class. + * @param classLoader + * The class loader of the class. + * @return true if given class is an interface. + * @throws NotFoundException + * This exception is thrown if a class is not found from within Javassist. + */ + boolean isInterface(String className, ClassLoader classLoader) throws NotFoundException; + + /** + * Returns true if the passed class implements the interface. + * + * @param className + * The name of the class. + * @param classLoader + * The class loader of the class. + * @param interfaceName + * The name of the interface. + * @return true if given class implements the interface. + */ + boolean implementsInterface(String className, ClassLoader classLoader, String interfaceName); + + /** + * Checks whether the passed className is a direct or indirect subclass of superClassName. + * + * @param className + * The fully qualified name of the class to check for. + * @param superClassName + * The fully qualified class name of the superclass. + * @param classPool + * The class pool where to search for. + * + * @return Whether the passed className is a direct or indirect subclass of superClassName. + */ + boolean subclassOf(String className, String superClassName, ClassPool classPool); +} \ No newline at end of file diff --git a/Agent/src/info/novatec/inspectit/agent/analyzer/IMatchPattern.java b/Agent/src/info/novatec/inspectit/agent/analyzer/IMatchPattern.java new file mode 100644 index 000000000..65e9fdea8 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/analyzer/IMatchPattern.java @@ -0,0 +1,30 @@ +package info.novatec.inspectit.agent.analyzer; + +import info.novatec.inspectit.agent.config.IConfigurationReader; + +/** + * The interface is used by some {@link IConfigurationReader} to try to match a given pattern to a + * string via {@link #match(String)}. The {@link #getPattern()} just returns the used pattern. + * + * @author Patrice Bouillet + * + */ +public interface IMatchPattern { + + /** + * Comparison test. + * + * @param match + * text to be matched against template + * @return If the test was successful. + */ + boolean match(String match); + + /** + * Returns the pattern. + * + * @return The pattern + */ + String getPattern(); + +} diff --git a/Agent/src/info/novatec/inspectit/agent/analyzer/IMatcher.java b/Agent/src/info/novatec/inspectit/agent/analyzer/IMatcher.java new file mode 100644 index 000000000..1a47d720f --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/analyzer/IMatcher.java @@ -0,0 +1,74 @@ +package info.novatec.inspectit.agent.analyzer; + +import java.util.List; + +import javassist.CtBehavior; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.NotFoundException; + +/** + * This interface is used for all implementations of the configuration matching system. There are + * different ones available because of the possibility to define sensors as superclasses or having a + * wildcard expression in its string. + * + * @author Patrice Bouillet + * + */ +public interface IMatcher { + + /** + * This method compares the class name. Returns true if the comparison is + * successful. + * + * @param classLoader + * The class loader of the given class. + * @param className + * The name of the class to load. + * @return true if the passed class name matches the defined one. + * @throws NotFoundException + * This exception is thrown if a class is not found from within Javassist. + */ + boolean compareClassName(ClassLoader classLoader, String className) throws NotFoundException; + + /** + * Returns all the matching methods of the passed class. + * + * @param classLoader + * The class loader of the given class. + * @param className + * The name of the class to load. + * @return A {@link List} containing all the {@link CtMethod} objects which matched successfully + * to this implementation. + * @throws NotFoundException + * This exception is thrown if a class is not found from within Javassist. + */ + List getMatchingMethods(ClassLoader classLoader, String className) throws NotFoundException; + + /** + * Returns all the matching constructors of the passed class. + * + * @param classLoader + * The class loader of the given class. + * @param className + * The name of the class to load. + * @return A {@link List} containing all the {@link CtConstructor} objects which matched + * successfully to this implementation. + * @throws NotFoundException + * This exception is thrown if a class is not found from within Javassist. + */ + List getMatchingConstructors(ClassLoader classLoader, String className) throws NotFoundException; + + /** + * This method checks all the parameters of the given {@link List} of {@link CtBehavior} objects + * if they match the configuration. If one method object is found which has parameters not + * matching to the configuration, it is removed from the list. + * + * @param methods + * The {@link List} of {@link CtBehavior} objects to check. + * @throws NotFoundException + * This exception is thrown if a class is not found from within Javassist. + */ + void checkParameters(List methods) throws NotFoundException; + +} diff --git a/Agent/src/info/novatec/inspectit/agent/analyzer/impl/AbstractMatcher.java b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/AbstractMatcher.java new file mode 100644 index 000000000..18ac41199 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/AbstractMatcher.java @@ -0,0 +1,40 @@ +package info.novatec.inspectit.agent.analyzer.impl; + +import info.novatec.inspectit.agent.analyzer.IClassPoolAnalyzer; +import info.novatec.inspectit.agent.analyzer.IMatcher; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; + +/** + * The abstract matcher class used to store the reference to the sensor configuration. + * + * @author Patrice Bouillet + * + */ +public abstract class AbstractMatcher implements IMatcher { + + /** + * The link to the class pool analyzer which is used by some Matcher implementations. + */ + protected final IClassPoolAnalyzer classPoolAnalyzer; + + /** + * The {@link UnregisteredSensorConfig} object to retrieve all the needed sensor informations + * from. + */ + protected final UnregisteredSensorConfig unregisteredSensorConfig; + + /** + * The only constructor which needs a reference to the {@link UnregisteredSensorConfig} instance + * of the corresponding configuration. + * + * @param classPoolAnalyzer + * The class pool analyzer. + * @param unregisteredSensorConfig + * The sensor configuration. + */ + public AbstractMatcher(IClassPoolAnalyzer classPoolAnalyzer, UnregisteredSensorConfig unregisteredSensorConfig) { + this.classPoolAnalyzer = classPoolAnalyzer; + this.unregisteredSensorConfig = unregisteredSensorConfig; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/analyzer/impl/AnnotationMatcher.java b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/AnnotationMatcher.java new file mode 100644 index 000000000..1cea94bf0 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/AnnotationMatcher.java @@ -0,0 +1,237 @@ +package info.novatec.inspectit.agent.analyzer.impl; + +import info.novatec.inspectit.agent.analyzer.IClassPoolAnalyzer; +import info.novatec.inspectit.agent.analyzer.IInheritanceAnalyzer; +import info.novatec.inspectit.agent.analyzer.IMatcher; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.NotFoundException; +import javassist.bytecode.AnnotationsAttribute; +import javassist.bytecode.AttributeInfo; +import javassist.bytecode.ClassFile; +import javassist.bytecode.FieldInfo; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.annotation.Annotation; + +/** + * This matcher filers that classes and methods based on the annotation class name defined in the + * {@link UnregisteredSensorConfig}. If the annotation supplied is targeting a Class, then all + * methods that are provided by delegate matchers of a Class that has annotation will be + * instrumented. Otherwise, if the Annotation is targeting the method, only methods that have this + * annotation will be instrumented. + * + * @author Ivan Senic + * + */ +public class AnnotationMatcher extends AbstractMatcher { + + /** + * The {@link IMatcher} delegator object to route the calls of all methods to. + */ + private IMatcher delegateMatcher; + + /** + * The inheritance checker used to check super-classes and interfaces. + */ + private IInheritanceAnalyzer inheritanceAnalyzer; + + /** + * The only constructor which needs a reference to the {@link UnregisteredSensorConfig} instance + * of the corresponding configuration. + * + * @param inheritanceAnalyzer + * Inheritance analyzer. + * @param classPoolAnalyzer + * The class pool analyzer. + * @param unregisteredSensorConfig + * The sensor configuration. + * @param delegateMatcher + * The {@link IMatcher} delegator object to route the calls of all methods to. + * @see AbstractMatcher + */ + public AnnotationMatcher(IInheritanceAnalyzer inheritanceAnalyzer, IClassPoolAnalyzer classPoolAnalyzer, UnregisteredSensorConfig unregisteredSensorConfig, IMatcher delegateMatcher) { + super(classPoolAnalyzer, unregisteredSensorConfig); + + this.inheritanceAnalyzer = inheritanceAnalyzer; + this.delegateMatcher = delegateMatcher; + } + + /** + * {@inheritDoc} + */ + public boolean compareClassName(ClassLoader classLoader, String className) throws NotFoundException { + return delegateMatcher.compareClassName(classLoader, className); + } + + /** + * {@inheritDoc} + */ + public List getMatchingMethods(ClassLoader classLoader, String className) throws NotFoundException { + List matchingMethods = delegateMatcher.getMatchingMethods(classLoader, className); + + boolean classHasAnnotation = checkClassForAnnotation(classLoader, className, unregisteredSensorConfig.getAnnotationClassName()); + + if (!classHasAnnotation) { + List notMatchingMethods = null; + for (CtMethod method : matchingMethods) { + @SuppressWarnings("unchecked") + List methodAttributesList = method.getMethodInfo2().getAttributes(); + boolean methodHasAnnotation = checkForAnnotation(methodAttributesList, unregisteredSensorConfig.getAnnotationClassName()); + if (!methodHasAnnotation) { + if (null == notMatchingMethods) { + notMatchingMethods = new ArrayList(); + } + notMatchingMethods.add(method); + } + } + if (null != notMatchingMethods) { + try { + matchingMethods.removeAll(notMatchingMethods); + } catch (UnsupportedOperationException exception) { + // list not supporting remove, do manually + List returnList = new ArrayList(); + returnList.addAll(matchingMethods); + returnList.removeAll(notMatchingMethods); + return returnList; + } + } + } + + return matchingMethods; + } + + /** + * {@inheritDoc} + */ + public List getMatchingConstructors(ClassLoader classLoader, String className) throws NotFoundException { + List matchingConstructors = delegateMatcher.getMatchingConstructors(classLoader, className); + + boolean classHasAnnotation = checkClassForAnnotation(classLoader, className, unregisteredSensorConfig.getAnnotationClassName()); + + if (!classHasAnnotation) { + List notMatchingConstructors = null; + for (CtConstructor constructor : matchingConstructors) { + @SuppressWarnings("unchecked") + List constructorAttributesList = constructor.getMethodInfo2().getAttributes(); + boolean constructorHasAnnotation = checkForAnnotation(constructorAttributesList, unregisteredSensorConfig.getAnnotationClassName()); + if (!constructorHasAnnotation) { + if (null == notMatchingConstructors) { + notMatchingConstructors = new ArrayList(); + } + notMatchingConstructors.add(constructor); + } + } + if (null != notMatchingConstructors) { + try { + matchingConstructors.removeAll(notMatchingConstructors); + } catch (UnsupportedOperationException exception) { + // list not supporting remove, do manually + List returnList = new ArrayList(); + returnList.addAll(matchingConstructors); + returnList.removeAll(notMatchingConstructors); + return returnList; + } + } + } + + return matchingConstructors; + } + + /** + * Checks if the class has the annotation with the given annotation name. This method will also + * check all the superclass and interfaces. + * + * @param classLoader + * Class loader. + * @param className + * Name of the class to check. + * @param annotationClassName + * Annotation name. + * @return True if annotation if found, false otherwise. + * @throws NotFoundException + * If type is not found. + */ + private boolean checkClassForAnnotation(ClassLoader classLoader, String className, String annotationClassName) throws NotFoundException { + CtClass clazz = classPoolAnalyzer.getClassPool(classLoader).get(className); + @SuppressWarnings("unchecked") + List classAttributesList = clazz.getClassFile2().getAttributes(); + if (checkForAnnotation(classAttributesList, annotationClassName)) { + return true; + } + + // check every super class + try { + Iterator iterator = inheritanceAnalyzer.getSuperclassIterator(classLoader, className); + while (iterator.hasNext()) { + @SuppressWarnings("unchecked") + List superClassAttributeList = iterator.next().getClassFile2().getAttributes(); + if (checkForAnnotation(superClassAttributeList, annotationClassName)) { + return true; + } + } + } catch (NotFoundException e) { // NOPMD NOCHK + // ignore + } + + // check every interface class + try { + Iterator iterator = inheritanceAnalyzer.getInterfaceIterator(classLoader, className); + while (iterator.hasNext()) { + @SuppressWarnings("unchecked") + List interfaceClassAttributeList = iterator.next().getClassFile2().getAttributes(); + if (checkForAnnotation(interfaceClassAttributeList, annotationClassName)) { + return true; + } + } + } catch (NotFoundException e) { // NOPMD NOCHK + // ignore + } + + return false; + } + + /** + * Checks if in the list of {@link AttributeInfo} objects exists any + * {@link AnnotationsAttribute} object that has information existence of the wanted annotation. + * Note that the attribute list should be acquired by {@link ClassFile#getAttributes()}, + * {@link MethodInfo#getAttributes()} or {@link FieldInfo#getAttributes()} methods. + * + * @param attributesList + * List of attributes. + * @param annotationClassName + * Name of the annotation to find. + * @return True if annotation could be located, false otherwise. + */ + private boolean checkForAnnotation(List attributesList, String annotationClassName) { + for (int i = 0; i < attributesList.size(); i++) { + AttributeInfo attributeInfo = (AttributeInfo) attributesList.get(i); + if (attributeInfo instanceof AnnotationsAttribute) { + AnnotationsAttribute annotationsAttribute = (AnnotationsAttribute) attributeInfo; + Annotation[] annotations = annotationsAttribute.getAnnotations(); + for (int j = 0; j < annotations.length; j++) { + if (annotations[j].getTypeName().equals(annotationClassName)) { + return true; + } + } + break; + } + } + return false; + } + + /** + * {@inheritDoc} + */ + public void checkParameters(List methods) throws NotFoundException { + delegateMatcher.checkParameters(methods); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/analyzer/impl/ByteCodeAnalyzer.java b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/ByteCodeAnalyzer.java new file mode 100644 index 000000000..e3a407c23 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/ByteCodeAnalyzer.java @@ -0,0 +1,394 @@ +package info.novatec.inspectit.agent.analyzer.impl; + +import info.novatec.inspectit.agent.analyzer.IByteCodeAnalyzer; +import info.novatec.inspectit.agent.analyzer.IClassPoolAnalyzer; +import info.novatec.inspectit.agent.analyzer.IMatcher; +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.config.StorageException; +import info.novatec.inspectit.agent.config.impl.MethodSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.PropertyAccessor.PropertyPathStart; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; +import info.novatec.inspectit.agent.hooking.IHookInstrumenter; +import info.novatec.inspectit.agent.hooking.impl.HookException; +import info.novatec.inspectit.communication.data.ParameterContentType; +import info.novatec.inspectit.spring.logger.Log; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javassist.ByteArrayClassPath; +import javassist.CannotCompileException; +import javassist.ClassPath; +import javassist.ClassPool; +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.LoaderClassPath; +import javassist.NotFoundException; + +import org.apache.commons.collections.CollectionUtils; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * The default implementation of the {@link IByteCodeAnalyzer} interface. First it tries to analyze + * the given byte code and collects all the methods which need to be instrumented in a Map. This is + * done in the {@link #analyze(byte[], String, ClassLoader)} method. Afterwards, the Map is passed + * to the {@link #instrument(Map)} method which will do the instrumentation. + * + * @author Patrice Bouillet + * @author Eduard Tudenhoefner + * + */ +@Component +public class ByteCodeAnalyzer implements IByteCodeAnalyzer { + + /** + * Log for the class. + */ + @Log + Logger log; + + /** + * The implementation of the hook instrumenter. + */ + private final IHookInstrumenter hookInstrumenter; + + /** + * The implementation of the configuration storage where all definitions of the user are stored. + */ + private final IConfigurationStorage configurationStorage; + + /** + * The class pool analyzer is used here to add the passed byte code to the class pool which is + * responsible for the class loader. + */ + private final IClassPoolAnalyzer classPoolAnalyzer; + + /** + * The default constructor which accepts two parameters which are needed. + * + * @param configurationStorage + * The configuration storage reference. + * @param hookInstrumenter + * The hook instrumenter reference. + * @param classPoolAnalyzer + * The class pool analyzer reference. + */ + @Autowired + public ByteCodeAnalyzer(IConfigurationStorage configurationStorage, IHookInstrumenter hookInstrumenter, IClassPoolAnalyzer classPoolAnalyzer) { + if (null == configurationStorage) { + throw new IllegalArgumentException("Configuration storage cannot be null!"); + } + if (null == hookInstrumenter) { + throw new IllegalArgumentException("Hook instrumenter cannot be null!"); + } + if (null == classPoolAnalyzer) { + throw new IllegalArgumentException("Class pool analyzer cannot be null!"); + } + this.configurationStorage = configurationStorage; + this.hookInstrumenter = hookInstrumenter; + this.classPoolAnalyzer = classPoolAnalyzer; + } + + /** + * {@inheritDoc} + */ + public byte[] analyzeAndInstrument(byte[] byteCode, String className, ClassLoader classLoader) { + // The reason to create a byte array class path here is to handle + // classes created at runtime (reflection / byte code engineering + // libraries etc.) and to get the real content of that class (think of + // classes modified by other java agents before.) + ClassPool classPool = classPoolAnalyzer.getClassPool(classLoader); + ClassPath classPath = null; + ClassPath loaderClassPath = null; + try { + if (null == byteCode) { + // this occurs if we are in the initialization phase and are instrumenting classes + // where we don't have the bytecode directly. Thus we try to load it. + byteCode = classPool.get(className).toBytecode(); + } + if (null != Thread.currentThread().getContextClassLoader() && classLoader != Thread.currentThread().getContextClassLoader()) { + // only use the context class loader if it is even set and not the same as the + // classloader being passed to the instrumentation + loaderClassPath = new LoaderClassPath(Thread.currentThread().getContextClassLoader()); + classPool.insertClassPath(loaderClassPath); + } + // the byte array classpath needs to be the last one to be the first for access + classPath = new ByteArrayClassPath(className, byteCode); + classPool.insertClassPath(classPath); + + byte[] instrumentedByteCode = null; + Map> behaviorToConfigMap = analyze(className, classLoader); + + // class loader delegation behaviors + List classLoaderDelegationBehaviors = analyzeForClassLoaderDelegation(className, classLoader); + + CtBehavior ctBehavior = null; + if (!behaviorToConfigMap.isEmpty()) { + ctBehavior = instrumentSensors(behaviorToConfigMap); + } + + if (!classLoaderDelegationBehaviors.isEmpty()) { + ctBehavior = instrumentClassLoader(classLoaderDelegationBehaviors); + } + + if (null != ctBehavior) { + instrumentedByteCode = ctBehavior.getDeclaringClass().toBytecode(); + } + + return instrumentedByteCode; + } catch (NotFoundException notFoundException) { + log.error("Error occurred instrumenting the byte code of class " + className, notFoundException); + return null; + } catch (IOException iOException) { + log.error("Error occurred instrumenting the byte code of class " + className, iOException); + return null; + } catch (CannotCompileException cannotCompileException) { + log.error("Error occurred instrumenting the byte code of class " + className, cannotCompileException); + return null; + } catch (HookException hookException) { + log.error("Error occurred instrumenting the byte code of class " + className, hookException); + return null; + } catch (StorageException storageException) { + log.error("Error occurred instrumenting the byte code of class " + className, storageException); + return null; + } finally { + // Remove the byte array class path from the class pool. The class + // loader now should know this class, thus it can be accessed + // through the standard way. + if (null != classPath) { + classPool.removeClassPath(classPath); + } + if (null != loaderClassPath) { + classPool.removeClassPath(loaderClassPath); + } + } + } + + /** + * Returns the list of {@link CtBehavior} that relate to the class loader delegation. + * + * @param className + * The name of the class. + * @param classLoader + * The class loader of the passed class. + * @return Returns the list of {@link CtBehavior} that relate to the class loader delegation. + * @throws NotFoundException + * Something could not be found. + */ + private List analyzeForClassLoaderDelegation(String className, ClassLoader classLoader) throws NotFoundException { + IMatcher matcher = configurationStorage.getClassLoaderDelegationMatcher(); + if (null != matcher && matcher.compareClassName(classLoader, className)) { + List behaviors = matcher.getMatchingMethods(classLoader, className); + if (CollectionUtils.isNotEmpty(behaviors)) { + matcher.checkParameters(behaviors); + return behaviors; + } + } + return Collections.emptyList(); + } + + /** + * The analyze method will analyze the passed byte code, class name and class loader and returns + * a {@link Map} with all matching methods to be instrumented. + * + * @param className + * The name of the class. + * @param classLoader + * The class loader of the passed class. + * @return Returns a {@link Map} with all found methods ({@link CtBehavior}) as the Key and a + * {@link List} of {@link UnregisteredSensorConfig} as the value. + * @throws NotFoundException + * Something could not be found. + * @throws StorageException + * Sensor could not be added. + */ + private Map> analyze(String className, ClassLoader classLoader) throws NotFoundException, StorageException { + Map> behaviorToConfigMap = new HashMap>(); + + // Iterating over all stored unregistered sensor configurations + for (UnregisteredSensorConfig unregisteredSensorConfig : configurationStorage.getUnregisteredSensorConfigs()) { + // try to match the class name first + IMatcher matcher = unregisteredSensorConfig.getMatcher(); + if (matcher.compareClassName(classLoader, className)) { + List behaviors; + // differentiate between constructors and methods. + if (unregisteredSensorConfig.isConstructor()) { + // the constructors + behaviors = matcher.getMatchingConstructors(classLoader, className); + } else { + // the methods + behaviors = matcher.getMatchingMethods(classLoader, className); + } + matcher.checkParameters(behaviors); + + // iterating over all methods which passed the matcher + for (CtBehavior behavior : behaviors) { + if (behaviorToConfigMap.containsKey(behavior)) { + // access the already initialized list and store the + // unregistered sensor configuration in it. + List configs = behaviorToConfigMap.get(behavior); + configs.add(unregisteredSensorConfig); + } else { + // key does not exist already, thus we have to + // create the list first. + List configs = new ArrayList(); + configs.add(unregisteredSensorConfig); + behaviorToConfigMap.put(behavior, configs); + } + } + } + } + + return behaviorToConfigMap; + } + + /** + * Instruments the methods in the {@link Map} and creates the appropriate + * {@link RegisteredSensorConfig} classes. + * + * @param methodToConfigMap + * The initialized {@link Map} which is filled by the + * {@link #analyze(byte[], String, ClassLoader)} method. + * @return Returns the instrumented byte code. + * @throws NotFoundException + * Something could not be found. + * @throws HookException + * The hook instrumenter generated an exception. + * @throws IOException + * The byte code could not be generated. + * @throws CannotCompileException + * The byte code could not be generated. + */ + private CtBehavior instrumentSensors(Map> methodToConfigMap) throws NotFoundException, HookException, IOException, CannotCompileException { + CtBehavior ctBehavior = null; + for (Map.Entry> entry : methodToConfigMap.entrySet()) { + ctBehavior = entry.getKey(); + List configs = entry.getValue(); + + List parameterTypes = new ArrayList(); + CtClass[] parameterClasses = ctBehavior.getParameterTypes(); + for (int pos = 0; pos < parameterClasses.length; pos++) { + parameterTypes.add(parameterClasses[pos].getName()); + } + + RegisteredSensorConfig rsc = new RegisteredSensorConfig(); + rsc.setTargetPackageName(ctBehavior.getDeclaringClass().getPackageName()); + rsc.setTargetClassName(ctBehavior.getDeclaringClass().getSimpleName()); + rsc.setTargetMethodName(ctBehavior.getName()); + rsc.setParameterTypes(parameterTypes); + rsc.setModifiers(ctBehavior.getModifiers()); + rsc.setCtBehavior(ctBehavior); + rsc.setConstructor(ctBehavior instanceof CtConstructor); + + // return type only for methods available, otherwise the return type is set to empty + // string. + if (!rsc.isConstructor()) { + CtMethod ctMethod = (CtMethod) ctBehavior; + rsc.setReturnType(ctMethod.getReturnType().getName()); + } + + for (UnregisteredSensorConfig usc : configs) { + rsc.addSensorTypeConfig(usc.getSensorTypeConfig()); + rsc.getSettings().putAll(usc.getSettings()); + + if (usc.isPropertyAccess()) { + for (PropertyPathStart propertyPathStart : usc.getPropertyAccessorList()) { + // Filter not meaningful property accessors. + if (isMeaningfulCapturing(propertyPathStart.getContentType(), rsc)) { + rsc.getPropertyAccessorList().add(propertyPathStart); + } + } + } + } + + rsc.setPropertyAccess(!rsc.getPropertyAccessorList().isEmpty()); + + // only when there is an enhanced Exception Sensor defined + if (configurationStorage.isExceptionSensorActivated() && configurationStorage.isEnhancedExceptionSensorActivated()) { + // iterate over the exception sensor types - currently there is only one + for (MethodSensorTypeConfig config : configurationStorage.getExceptionSensorTypes()) { + // need to add the exception sensor config separately, because otherwise it + // would be added to the other method hooks, but the exception sensor is a + // constructor hook + rsc.setExceptionSensorTypeConfig(config); + } + } + + if (!rsc.isConstructor()) { + hookInstrumenter.addMethodHook((CtMethod) ctBehavior, rsc); + } else { + hookInstrumenter.addConstructorHook((CtConstructor) ctBehavior, rsc); + } + } + return ctBehavior; + } + + /** + * Instruments the methods in the {@link List} with the class loader delegation hook. + * + * @param classLoaderDelegationBehaviors + * {@link CtBehavior}s that relate to the class loader boot delegation and have to be + * instrumented in different way that the normal user specified instrumentation. + * + * @return Returns the {@link CtBehavior}. + * @throws NotFoundException + * Something could not be found. + * @throws HookException + * The hook instrumenter generated an exception. + * @throws IOException + * The byte code could not be generated. + * @throws CannotCompileException + * The byte code could not be generated. + */ + private CtBehavior instrumentClassLoader(List classLoaderDelegationBehaviors) throws NotFoundException, HookException, IOException, CannotCompileException { + CtBehavior ctBehavior = null; + if (CollectionUtils.isNotEmpty(classLoaderDelegationBehaviors)) { + for (CtBehavior clDelegationBehavior : classLoaderDelegationBehaviors) { + ctBehavior = clDelegationBehavior; + hookInstrumenter.addClassLoaderDelegationHook((CtMethod) ctBehavior); + } + } + return ctBehavior; + } + + /** + * Checks whether the property accessor is meaningful. Please note that during the creation of + * the property accessor certain checks are already in place. For example it is checked that no + * return value capturing is set on a constructor. Please ensure that checks that could be done + * at creation time are already performed at this time ({@link + * info.novatec.inspectit.agent.config.impl.ConfigurationStorage.addSensor()}). + * + * Certain checks cannot be done on creation time. One example is the return value capturing on + * method defining a void return type. At creation time the information that the method the + * sensor is attached to has in fact no return value is not known. + * + * @param type + * the type of capturing + * @param rsc + * the sensor configuration + * @return if this property accessor is meaningful. + */ + private boolean isMeaningfulCapturing(ParameterContentType type, RegisteredSensorConfig rsc) { + // Return value capturing on constructors is not meaningful (property accessors should + // never be placed on constructors anyway, so this is just an additional layer of safety). + if (ParameterContentType.RETURN.equals(type) && rsc.isConstructor()) { + return false; + } + + // Return value capturing for void returning methods is just not meaningful. + if (ParameterContentType.RETURN.equals(type) && !rsc.isConstructor() && "void".equals(rsc.getReturnType())) { + return false; + } + + return true; + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/analyzer/impl/ClassPoolAnalyzer.java b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/ClassPoolAnalyzer.java new file mode 100644 index 000000000..7c4a3172d --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/ClassPoolAnalyzer.java @@ -0,0 +1,162 @@ +package info.novatec.inspectit.agent.analyzer.impl; + +import info.novatec.inspectit.agent.analyzer.IClassPoolAnalyzer; +import info.novatec.inspectit.spring.logger.Log; +import info.novatec.inspectit.util.WeakList; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.LoaderClassPath; +import javassist.Modifier; +import javassist.NotFoundException; + +import org.slf4j.Logger; +import org.springframework.stereotype.Component; + +/** + * This class provides some methods to help to work with the {@link ClassPool}. + * + * @author Patrice Bouillet + * + */ +@Component +public class ClassPoolAnalyzer implements IClassPoolAnalyzer { + + /** + * The logger of this class. + */ + @Log + Logger log; + + /** + * A weak list to save references to the class loaders. + */ + private static WeakList classLoaders = new WeakList(); + + /** + * Contains a mapping from the {@link ClassPool} to the {@link ClassLoader} objects. + */ + private static Map map = new WeakHashMap(); + + /** + * {@inheritDoc} + */ + public CtMethod[] getMethodsForClassName(ClassLoader classLoader, String className) { + try { + CtClass cc = this.getClassPool(classLoader).get(className); + + // exclude interface classes, cannot instrument them! + if (!Modifier.isInterface(cc.getModifiers())) { + return cc.getDeclaredMethods(); + } + } catch (NotFoundException e) { + log.error("NotFoundException caught for class: " + className); + } catch (RuntimeException e) { + log.error("Class which generated a runtime exception: " + className); + log.error(e.getMessage()); + } + + return new CtMethod[0]; + } + + /** + * {@inheritDoc} + */ + public CtConstructor[] getConstructorsForClassName(ClassLoader classLoader, String className) { + try { + CtClass cc = this.getClassPool(classLoader).get(className); + List constructorList = new ArrayList(); + + // exclude interface classes, cannot instrument them! + if (!Modifier.isInterface(cc.getModifiers())) { + CtConstructor[] constructors = cc.getDeclaredConstructors(); + for (int i = 0; i < constructors.length; i++) { + CtConstructor ctConstructor = constructors[i]; + // only add real constructors, no class initializers + if (ctConstructor.isConstructor()) { + constructorList.add(ctConstructor); + } + } + return constructorList.toArray(new CtConstructor[constructorList.size()]); + } + } catch (NotFoundException e) { + log.error("NotFoundException caught for class: " + className); + } catch (RuntimeException e) { + log.error("Class which generated a runtime exception: " + className); + log.error(e.getMessage()); + } + + return new CtConstructor[0]; + } + + /** + * {@inheritDoc} + */ + public ClassPool addClassLoader(ClassLoader classLoader) { + if (null == classLoader) { + return ClassPool.getDefault(); + } + if (!classLoaders.contains(classLoader)) { + return this.copyHierarchy(classLoader); + } + + return map.get(classLoader); + } + + /** + * {@inheritDoc} + */ + public ClassPool getClassPool(ClassLoader classLoader) { + if (null == classLoader) { + // Return the default classpool if we don't have the mapping yet. + return ClassPool.getDefault(); + } + ClassPool cp = (ClassPool) map.get(classLoader); + if (null == cp) { + cp = this.addClassLoader(classLoader); + } + return cp; + } + + /** + * Copy the hierarchy from the given classloader and build new classpool objects. + * + * @param classLoader + * The class loader. + * @return The newly created ClassPool referring to this class loader. + */ + private ClassPool copyHierarchy(ClassLoader classLoader) { + // Check if current class loader was already seen before + if (!classLoaders.contains(classLoader)) { + classLoaders.add(classLoader); + } + + ClassPool cp = null; + if (null != classLoader.getParent() && !classLoaders.contains(classLoader.getParent())) { + // If the class loader has got a parent one and was not seen before + // -> initialize that one first + cp = new ClassPool(this.copyHierarchy(classLoader.getParent())); + } else if (null != classLoader.getParent()) { + // Parent class loader was seen and initialized before, we only care + // about the current one and set the parent class loader. + cp = new ClassPool(map.get(classLoader.getParent())); + } else { + // Class loader has got no parent ( bootstrap class loader ) + cp = new ClassPool(ClassPool.getDefault()); + } + + cp.insertClassPath(new LoaderClassPath(classLoader)); + // Map the class loader to the ClassPool + map.put(classLoader, cp); + + return cp; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/analyzer/impl/DirectMatcher.java b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/DirectMatcher.java new file mode 100644 index 000000000..5f18ca669 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/DirectMatcher.java @@ -0,0 +1,122 @@ +package info.novatec.inspectit.agent.analyzer.impl; + +import info.novatec.inspectit.agent.analyzer.IClassPoolAnalyzer; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.Modifier; +import javassist.NotFoundException; + +/** + * This implementation compares directly the class name, method name and parameter types. 'Direct' + * denotes comparing them by calling the equals method of the String objects. + * + * @author Patrice Bouillet + * + */ +public class DirectMatcher extends AbstractMatcher { + + /** + * The only constructor which needs a reference to the {@link UnregisteredSensorConfig} instance + * of the corresponding configuration. + * + * @param classPoolAnalyzer + * The class pool analyzer. + * @param unregisteredSensorConfig + * The sensor configuration. + * @see AbstractMatcher + */ + public DirectMatcher(IClassPoolAnalyzer classPoolAnalyzer, UnregisteredSensorConfig unregisteredSensorConfig) { + super(classPoolAnalyzer, unregisteredSensorConfig); + } + + /** + * {@inheritDoc} + */ + public boolean compareClassName(ClassLoader classLoader, String className) throws NotFoundException { + return unregisteredSensorConfig.getTargetClassName().equals(className); + } + + /** + * {@inheritDoc} + */ + public List getMatchingMethods(ClassLoader classLoader, String className) throws NotFoundException { + CtMethod[] methods = classPoolAnalyzer.getMethodsForClassName(classLoader, className); + + if (methods.length > 0) { + List matchingMethods = new ArrayList(); + + for (CtMethod method : methods) { + // skip abstract and native methods + if (!Modifier.isAbstract(method.getModifiers()) && !Modifier.isNative(method.getModifiers())) { + if (method.getName().equals(unregisteredSensorConfig.getTargetMethodName())) { + matchingMethods.add(method); + } + } + } + + return matchingMethods; + } + + return Collections.emptyList(); + } + + /** + * {@inheritDoc} + */ + public List getMatchingConstructors(ClassLoader classLoader, String className) throws NotFoundException { + CtConstructor[] constructors = classPoolAnalyzer.getConstructorsForClassName(classLoader, className); + + if (constructors.length > 0) { + // if the name of the target method name is '', every + // constructor will be added and checked + if ("".equals(unregisteredSensorConfig.getTargetMethodName())) { + List constructorList = Arrays.asList(constructors); + // we have to create a new arraylist here, as the list created + // the line above does not support the remove operator in the + // returned iterator... + return new ArrayList(constructorList); + } + } + + return Collections.emptyList(); + } + + /** + * {@inheritDoc} + */ + public void checkParameters(List methods) throws NotFoundException { + if (!unregisteredSensorConfig.isIgnoreSignature()) { + List parameterTypes = unregisteredSensorConfig.getParameterTypes(); + for (Iterator iterator = methods.iterator(); iterator.hasNext();) { + CtBehavior behaviour = iterator.next(); + + // get all the parameter types from the method + CtClass[] args = behaviour.getParameterTypes(); + + // simple check if the parameter count is equal + if (null != parameterTypes && parameterTypes.size() == args.length) { + // compare every parameter definition + for (int i = 0; i < parameterTypes.size(); i++) { + if (!parameterTypes.get(i).equals(args[i].getName())) { + iterator.remove(); + i = parameterTypes.size(); + } + } + } else if (args.length >= 0) { + iterator.remove(); + } + } + } + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/analyzer/impl/IndirectMatcher.java b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/IndirectMatcher.java new file mode 100644 index 000000000..4b50ab2d7 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/IndirectMatcher.java @@ -0,0 +1,170 @@ +package info.novatec.inspectit.agent.analyzer.impl; + +import info.novatec.inspectit.agent.analyzer.IClassPoolAnalyzer; +import info.novatec.inspectit.agent.analyzer.IMatchPattern; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.Modifier; +import javassist.NotFoundException; + +/** + * The indirect matcher is used for a sensor configuration which contains a pattern somewhere in the + * class name, method name or one of the parameter types. + * + * @author Patrice Bouillet + * + */ +public class IndirectMatcher extends AbstractMatcher { + + /** + * The {@link DirectMatcher} object is needed for Strings which are not containing a pattern. + * Hence a small gain in performance should be accomplished. + */ + private DirectMatcher directMatcher; + + /** + * The pattern object of the class name. + */ + private IMatchPattern classNamePattern = null; + + /** + * The pattern object of the method name. + */ + private IMatchPattern methodNamePattern = null; + + /** + * All patterns of the parameters. Only maps those parameters which are really a pattern. + */ + private Map parameterTypesPatterns = new HashMap(); + + /** + * The only constructor which needs a reference to the {@link UnregisteredSensorConfig} instance + * of the corresponding configuration. + * + * @param classPoolAnalyzer + * The class pool analyzer. + * @param unregisteredSensorConfig + * The sensor configuration. + * @see AbstractMatcher + */ + public IndirectMatcher(IClassPoolAnalyzer classPoolAnalyzer, UnregisteredSensorConfig unregisteredSensorConfig) { + super(classPoolAnalyzer, unregisteredSensorConfig); + + directMatcher = new DirectMatcher(classPoolAnalyzer, unregisteredSensorConfig); + + if (SimpleMatchPattern.isPattern(unregisteredSensorConfig.getTargetClassName())) { + classNamePattern = new SimpleMatchPattern(unregisteredSensorConfig.getTargetClassName()); + } + + if (SimpleMatchPattern.isPattern(unregisteredSensorConfig.getTargetMethodName())) { + methodNamePattern = new SimpleMatchPattern(unregisteredSensorConfig.getTargetMethodName()); + } + + if (null != unregisteredSensorConfig.getParameterTypes()) { + for (String parameter : unregisteredSensorConfig.getParameterTypes()) { + if (SimpleMatchPattern.isPattern(parameter)) { + parameterTypesPatterns.put(parameter, new SimpleMatchPattern(parameter)); + } + } + } + } + + /** + * {@inheritDoc} + */ + public boolean compareClassName(ClassLoader classLoader, String className) throws NotFoundException { + if (null == classNamePattern) { + return directMatcher.compareClassName(classLoader, className); + } + + return classNamePattern.match(className); + } + + /** + * {@inheritDoc} + */ + public List getMatchingMethods(ClassLoader classLoader, String className) throws NotFoundException { + if (null == methodNamePattern) { + return directMatcher.getMatchingMethods(classLoader, className); + } + + CtMethod[] methods = classPoolAnalyzer.getMethodsForClassName(classLoader, className); + + if (null != methods && methods.length > 0) { + List matchingMethods = new ArrayList(); + + for (CtMethod method : methods) { + // skip abstract and native methods + if (!Modifier.isAbstract(method.getModifiers()) && !Modifier.isNative(method.getModifiers())) { + if (methodNamePattern.match(method.getName())) { + matchingMethods.add(method); + } + } + } + + return matchingMethods; + } + + return Collections.emptyList(); + } + + /** + * {@inheritDoc} + */ + public List getMatchingConstructors(ClassLoader classLoader, String className) throws NotFoundException { + // no pattern allowed for the constructor, has to be specified directly. + // That is to disallow something like *init* (which would include the + // constructor). + if (null != methodNamePattern) { + return Collections.emptyList(); + } + + return directMatcher.getMatchingConstructors(classLoader, className); + } + + /** + * {@inheritDoc} + */ + public void checkParameters(List methods) throws NotFoundException { + if (!unregisteredSensorConfig.isIgnoreSignature()) { + List parameterTypes = unregisteredSensorConfig.getParameterTypes(); + if (parameterTypesPatterns.isEmpty()) { + directMatcher.checkParameters(methods); + } else if (null != parameterTypes) { + for (Iterator iterator = methods.iterator(); iterator.hasNext();) { + CtBehavior behaviour = iterator.next(); + // get all the parameter types from the behavior + CtClass[] ctClasses = behaviour.getParameterTypes(); + + // simple check if the parameter count is equal + if (parameterTypes.size() == ctClasses.length) { + // compare every parameter definition + for (int i = 0; i < parameterTypes.size(); i++) { + if (parameterTypesPatterns.containsKey(parameterTypes.get(i))) { + // Parameter definition is a pattern + if (!((IMatchPattern) parameterTypesPatterns.get(parameterTypes.get(i))).match(ctClasses[i].getName())) { + iterator.remove(); + } + } else if (!parameterTypes.get(i).equals(ctClasses[i].getName())) { + iterator.remove(); + } + } + } else { + iterator.remove(); + } + } + } + } + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/analyzer/impl/InheritanceAnalyzer.java b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/InheritanceAnalyzer.java new file mode 100644 index 000000000..e07377939 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/InheritanceAnalyzer.java @@ -0,0 +1,238 @@ +package info.novatec.inspectit.agent.analyzer.impl; + +import info.novatec.inspectit.agent.analyzer.IClassPoolAnalyzer; +import info.novatec.inspectit.agent.analyzer.IInheritanceAnalyzer; +import info.novatec.inspectit.spring.logger.Log; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * The default implementation of the {@link IInheritanceAnalyzer} interface. + * + * @author Patrice Bouillet + * @author Eduard Tudenhoefner + * + */ +@Component +public class InheritanceAnalyzer implements IInheritanceAnalyzer { + + /** + * The logger of this class. + */ + @Log + Logger log; + + /** + * Set of logged classes for which the interfaces can not be found in + * {@link #addInterfaceExtends(List, CtClass)}. + */ + private Set loggedClassesSet = new HashSet(); + + /** + * The class pool analyzer is used by the {@link #getSuperclassIterator(ClassLoader, String)} + * and {@link #getInterfaceIterator(ClassLoader, String)} methods to access the right class. + */ + private final IClassPoolAnalyzer classPoolAnalyzer; + + /** + * The default constructor accepting one parameter. + * + * @param classPoolAnalyzer + * The class pool analyzer. + */ + @Autowired + public InheritanceAnalyzer(IClassPoolAnalyzer classPoolAnalyzer) { + this.classPoolAnalyzer = classPoolAnalyzer; + } + + /** + * {@inheritDoc} + */ + public Iterator getSuperclassIterator(ClassLoader classLoader, String className) throws NotFoundException { + // retrieve the correct class pool + ClassPool classPool = classPoolAnalyzer.getClassPool(classLoader); + CtClass clazz = classPool.get(className); + return new SuperclassIterator(clazz); + } + + /** + * Iterator implementation to iterate over all superclasses of a class. + * + * @author Patrice Bouillet + * + */ + private static class SuperclassIterator implements Iterator { + + /** + * The current super class. + */ + private CtClass superClass; + + /** + * The iterator has to be initialized with a {@link CtClass} object where all super classes + * are taken from. + * + * @param clazz + * The root class. + * @throws NotFoundException + * This exception is thrown if a class is not found from within Javassist. + */ + public SuperclassIterator(CtClass clazz) throws NotFoundException { + superClass = clazz; + } + + /** + * {@inheritDoc} + */ + public boolean hasNext() { + try { + return superClass.getSuperclass() != null; + } catch (NotFoundException e) { + return false; + } + } + + /** + * {@inheritDoc} + */ + public CtClass next() { + try { + superClass = superClass.getSuperclass(); + } catch (NotFoundException e) { + throw new NoSuchElementException(e.getMessage()); // NOPMD + } + + if (null == superClass) { + throw new NoSuchElementException(); + } + + return superClass; + } + + /** + * {@inheritDoc} + */ + public void remove() { + throw new UnsupportedOperationException(); + } + + } + + /** + * {@inheritDoc} + */ + public Iterator getInterfaceIterator(ClassLoader classLoader, String className) throws NotFoundException { + // retrieve the correct class pool + ClassPool classPool = classPoolAnalyzer.getClassPool(classLoader); + CtClass ctClass = classPool.get(className); + + List interfaces = new ArrayList(); + while (null != ctClass) { + addInterfaceExtends(interfaces, ctClass); + ctClass = ctClass.getSuperclass(); + } + + return interfaces.iterator(); + } + + /** + * Adds all interfaces to this list, including the one which are extended by other interfaces. + * + * @param interfaces + * The list to add the interfaces to. + * @param ctClass + * The class to get the interfaces from. + * @throws NotFoundException + * This exception is thrown if a class is not found from within Javassist. + */ + private void addInterfaceExtends(List interfaces, CtClass ctClass) throws NotFoundException { + String[] ifs = null; + try { + ifs = ctClass.getClassFile2().getInterfaces(); + } catch (Exception e) { + String className = ctClass.getName(); + if (loggedClassesSet.add(className)) { + log.warn("Not possible to load interfaces for class " + className + "."); + } + } + if (null != ifs) { + int num = ifs.length; + CtClass[] ctClasses = new CtClass[num]; + for (int i = 0; i < num; ++i) { + try { + ctClasses[i] = ctClass.getClassPool().get(ifs[i]); + } catch (NotFoundException e) { // NOPMD NOCHK + // ignore + } + } + + for (int i = 0; i < ctClasses.length; i++) { + if (null != ctClasses[i]) { + CtClass interfaceCtClass = ctClasses[i]; + interfaces.add(interfaceCtClass); + addInterfaceExtends(interfaces, interfaceCtClass); + } + } + } + } + + /** + * {@inheritDoc} + */ + public boolean isInterface(String className, ClassLoader classLoader) throws NotFoundException { + // retrieve the correct class pool + ClassPool classPool = classPoolAnalyzer.getClassPool(classLoader); + + CtClass actClass = classPool.get(className); + return actClass.isInterface(); + } + + /** + * {@inheritDoc} + */ + public boolean implementsInterface(String className, ClassLoader classLoader, String interfaceName) { + try { + for (Iterator interfaceIterator = getInterfaceIterator(classLoader, className); interfaceIterator.hasNext();) { + CtClass ctInterface = interfaceIterator.next(); + String name = ctInterface.getName(); + if (name.equalsIgnoreCase(interfaceName)) { + return true; + } + } + } catch (NotFoundException e) { + return false; + } + + return false; + } + + /** + * {@inheritDoc} + */ + public boolean subclassOf(String className, String superClassName, ClassPool classPool) { + try { + CtClass superClass = classPool.get(superClassName); + CtClass clazz = classPool.get(className); + + if (clazz.subclassOf(superClass)) { + return true; + } + } catch (NotFoundException e) { + return false; + } + return false; + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/analyzer/impl/InterfaceMatcher.java b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/InterfaceMatcher.java new file mode 100644 index 000000000..2e187fafe --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/InterfaceMatcher.java @@ -0,0 +1,98 @@ +package info.novatec.inspectit.agent.analyzer.impl; + +import info.novatec.inspectit.agent.analyzer.IClassPoolAnalyzer; +import info.novatec.inspectit.agent.analyzer.IInheritanceAnalyzer; +import info.novatec.inspectit.agent.analyzer.IMatcher; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; + +import java.util.Iterator; +import java.util.List; + +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.NotFoundException; + +/** + * The interface matcher implementation is used to check if the class name of the configuration is + * equal to one of the implemented interfaces of the passed class. All of the calls to these methods + * are mainly delegated to either an {@link DirectMatcher} or an {@link IndirectMatcher}, depending + * if the configuration is virtual (contains a pattern). + * + * @author Patrice Bouillet + * + */ +public class InterfaceMatcher extends AbstractMatcher { + + /** + * The inheritance checker used to check if an interface matches. + */ + private final IInheritanceAnalyzer inheritanceAnalyzer; + + /** + * The {@link IMatcher} delegator object to route the calls of all methods to. + */ + private IMatcher delegateMatcher; + + /** + * The only constructor which needs a reference to the {@link UnregisteredSensorConfig} instance + * of the corresponding configuration. + * + * @param inheritanceAnalyzer + * The inheritance analyzer. + * @param classPoolAnalyzer + * The class pool analyzer. + * @param unregisteredSensorConfig + * The sensor configuration. + * @see AbstractMatcher + */ + public InterfaceMatcher(IInheritanceAnalyzer inheritanceAnalyzer, IClassPoolAnalyzer classPoolAnalyzer, UnregisteredSensorConfig unregisteredSensorConfig) { + super(classPoolAnalyzer, unregisteredSensorConfig); + + this.inheritanceAnalyzer = inheritanceAnalyzer; + + if (unregisteredSensorConfig.isVirtual()) { + delegateMatcher = new IndirectMatcher(classPoolAnalyzer, unregisteredSensorConfig); + } else { + delegateMatcher = new DirectMatcher(classPoolAnalyzer, unregisteredSensorConfig); + } + } + + /** + * {@inheritDoc} + */ + public boolean compareClassName(ClassLoader classLoader, String className) throws NotFoundException { + Iterator i = inheritanceAnalyzer.getInterfaceIterator(classLoader, className); + while (i.hasNext()) { + CtClass clazz = i.next(); + if (delegateMatcher.compareClassName(classLoader, clazz.getName())) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + public final List getMatchingMethods(ClassLoader classLoader, String className) throws NotFoundException { + return delegateMatcher.getMatchingMethods(classLoader, className); + } + + /** + * {@inheritDoc} + */ + public List getMatchingConstructors(ClassLoader classLoader, String className) throws NotFoundException { + return delegateMatcher.getMatchingConstructors(classLoader, className); + } + + /** + * {@inheritDoc} + */ + public final void checkParameters(List methods) throws NotFoundException { + delegateMatcher.checkParameters(methods); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/analyzer/impl/ModifierMatcher.java b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/ModifierMatcher.java new file mode 100644 index 000000000..2c13ecd5b --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/ModifierMatcher.java @@ -0,0 +1,167 @@ +package info.novatec.inspectit.agent.analyzer.impl; + +import info.novatec.inspectit.agent.analyzer.IClassPoolAnalyzer; +import info.novatec.inspectit.agent.analyzer.IMatcher; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; + +import java.util.ArrayList; +import java.util.List; + +import javassist.CtBehavior; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.Modifier; +import javassist.NotFoundException; + +/** + * The modifier matcher is used to check if the modifier of class methods correspond to the modifier + * value set in the {@link UnregisteredSensorConfig}. Furthermore, all the call to this class are + * delegated to delegate matcher specified in the constructor. + * + * @author Ivan Senic + * + */ +public class ModifierMatcher extends AbstractMatcher { + + /** + * Flag marker for DEFAULT modifier. Needed to keep track if the default is also specified in + * the list of modifiers. + */ + public static final int DEFAULT = 0x8000; + + /** + * The {@link IMatcher} delegator object to route the calls of all methods to. + */ + private IMatcher delegateMatcher; + + /** + * The only constructor which needs a reference to the {@link UnregisteredSensorConfig} instance + * of the corresponding configuration. + * + * @param classPoolAnalyzer + * The class pool analyzer. + * @param unregisteredSensorConfig + * The sensor configuration. + * @param delegateMatcher + * The {@link IMatcher} delegator object to route the calls of all methods to. + * @see AbstractMatcher + */ + public ModifierMatcher(IClassPoolAnalyzer classPoolAnalyzer, UnregisteredSensorConfig unregisteredSensorConfig, IMatcher delegateMatcher) { + super(classPoolAnalyzer, unregisteredSensorConfig); + + this.delegateMatcher = delegateMatcher; + } + + /** + * {@inheritDoc} + */ + public boolean compareClassName(ClassLoader classLoader, String className) throws NotFoundException { + return delegateMatcher.compareClassName(classLoader, className); + } + + /** + * {@inheritDoc} + */ + public List getMatchingMethods(ClassLoader classLoader, String className) throws NotFoundException { + List matchingMethods = delegateMatcher.getMatchingMethods(classLoader, className); + List notMatchingMethods = null; + + for (CtMethod method : matchingMethods) { + boolean modiferMatched = false; + if (method.getModifiers() == unregisteredSensorConfig.getModifiers()) { + modiferMatched = true; + } else if (Modifier.isPublic(method.getModifiers()) && Modifier.isPublic(unregisteredSensorConfig.getModifiers())) { + modiferMatched = true; + } else if (Modifier.isProtected(method.getModifiers()) && Modifier.isProtected(unregisteredSensorConfig.getModifiers())) { + modiferMatched = true; + } else if (Modifier.isPrivate(method.getModifiers()) && Modifier.isPrivate(unregisteredSensorConfig.getModifiers())) { + modiferMatched = true; + } else if (Modifier.isPackage(method.getModifiers()) && isDefault(unregisteredSensorConfig.getModifiers())) { + modiferMatched = true; + } + + if (!modiferMatched) { + if (null == notMatchingMethods) { + notMatchingMethods = new ArrayList(); + } + notMatchingMethods.add(method); + } + } + + if (null != notMatchingMethods) { + try { + matchingMethods.removeAll(notMatchingMethods); + } catch (UnsupportedOperationException exception) { + // if list can not perform remove do it manually + List returnList = new ArrayList(); + returnList.addAll(matchingMethods); + returnList.removeAll(notMatchingMethods); + return returnList; + } + } + + return matchingMethods; + } + + /** + * {@inheritDoc} + */ + public List getMatchingConstructors(ClassLoader classLoader, String className) throws NotFoundException { + List matchingConstructors = delegateMatcher.getMatchingConstructors(classLoader, className); + List notMatchingConstructors = null; + + for (CtConstructor constructor : matchingConstructors) { + boolean modiferMatched = false; + if (constructor.getModifiers() == unregisteredSensorConfig.getModifiers()) { + modiferMatched = true; + } else if (Modifier.isPublic(constructor.getModifiers()) && Modifier.isPublic(unregisteredSensorConfig.getModifiers())) { + modiferMatched = true; + } else if (Modifier.isProtected(constructor.getModifiers()) && Modifier.isProtected(unregisteredSensorConfig.getModifiers())) { + modiferMatched = true; + } else if (Modifier.isPrivate(constructor.getModifiers()) && Modifier.isPrivate(unregisteredSensorConfig.getModifiers())) { + modiferMatched = true; + } else if (Modifier.isPackage(constructor.getModifiers()) && isDefault(unregisteredSensorConfig.getModifiers())) { + modiferMatched = true; + } + + if (!modiferMatched) { + if (null == notMatchingConstructors) { + notMatchingConstructors = new ArrayList(); + } + notMatchingConstructors.add(constructor); + } + } + + if (null != notMatchingConstructors) { + try { + matchingConstructors.removeAll(notMatchingConstructors); + } catch (UnsupportedOperationException exception) { + // if list can not perform remove do it manually + List returnList = new ArrayList(); + returnList.addAll(matchingConstructors); + returnList.removeAll(notMatchingConstructors); + return returnList; + } + } + + return matchingConstructors; + } + + /** + * {@inheritDoc} + */ + public void checkParameters(List methods) throws NotFoundException { + delegateMatcher.checkParameters(methods); + } + + /** + * Returns if the DEFAULT modifier bit is present in given modifier int value. + * + * @param mod + * Modifier as int value. + * @return True if DEFAULT modifier is present. + */ + public static boolean isDefault(int mod) { + return (mod & DEFAULT) != 0; + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/analyzer/impl/SimpleMatchPattern.java b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/SimpleMatchPattern.java new file mode 100644 index 000000000..c5ad3c396 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/SimpleMatchPattern.java @@ -0,0 +1,167 @@ +package info.novatec.inspectit.agent.analyzer.impl; + +import info.novatec.inspectit.agent.analyzer.IMatchPattern; + +import java.util.ArrayList; +import java.util.List; + +/** + * Pattern matcher for simple strings with '*' wildcard characters. The supplied template may take + * the form "xxx" for an exact match, "xxx*" for a match on leading characters only, "*xxx" to match + * on trailing characters only, or "xxx*yyy" to match on both leading and trailing. It can also + * include multiple '*' characters when more than one part of the match is wildcarded. + *

+ * IMPORTANT: The class code is copied/taken from IBM developers Works. + * Original author is Dennis Sosnoski. License info can be found here. + * + * @author Dennis Sosnoski + */ +public class SimpleMatchPattern implements IMatchPattern { + + /** + * Text components to be matched. + */ + private String[] components; + + /** + * Flag for leading text to be matched. + */ + private boolean isLeadText; + + /** + * Flag for trailing text to be matched. + */ + private boolean isTrailText; + + /** + * The template string used for matching. + */ + private String template; + + /** + * Is set to true if the template equals '*'. As this matches everything, we skip the whole + * compare process. + */ + private boolean everything = false; + + /** + * Constructor. + * + * @param template + * match text template + */ + public SimpleMatchPattern(final String template) { + if ("*".equals(template)) { + everything = true; + isLeadText = false; + isTrailText = false; + return; + } + + int mark = template.indexOf('*'); + List comps = new ArrayList(); + + if (mark < 0) { + // set up for exact match + comps.add(template); + isLeadText = true; + isTrailText = true; + } else { + // handle leading wildcard + int base = 0; + if (mark == 0) { + isLeadText = false; + base = 1; + mark = template.indexOf('*', 1); + } else { + isLeadText = true; + } + // loop for all text components to be matched + int limit = template.length() - 1; + while (mark > 0) { + comps.add(template.substring(base, mark)); + base = mark + 1; + if (mark == limit) { + break; + } + mark = template.indexOf('*', base); + } + comps.add(template.substring(base)); + isTrailText = mark != limit; + } + + // save array of text components to be matched + components = (String[]) comps.toArray(new String[comps.size()]); + + this.template = template; + } + + /** + * {@inheritDoc} + */ + public final boolean match(String match) { + if (everything) { + return true; + } + + // first check for required leading text + int start = 0; + int end = match.length(); + int index = 0; + if (isLeadText) { + if (match.startsWith(components[0])) { + start = components[0].length(); + index = 1; + } else { + return false; + } + } + + // next check for required trailing text + int limit = components.length; + if (isTrailText) { + limit--; + if (match.endsWith(components[limit])) { + end -= components[limit].length(); + } else { + return false; + } + } + + // finally match all floating comparison components + while (index < limit) { + String comp = components[index++]; + start = match.indexOf(comp, start); + if (start >= 0) { + start += comp.length(); + if (start > end) { + return false; + } + } else { + return false; + } + } + return true; + } + + /** + * {@inheritDoc} + */ + public String getPattern() { + return template; + } + + /** + * Checks if the supplied {@link String} is a pattern. + * + * @param txt + * The text to check for. + * @return Returns if the supplied String is a pattern. + */ + public static boolean isPattern(String txt) { + return txt.indexOf('*') > -1; + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/analyzer/impl/SuperclassMatcher.java b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/SuperclassMatcher.java new file mode 100644 index 000000000..5dc9e8c0e --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/SuperclassMatcher.java @@ -0,0 +1,98 @@ +package info.novatec.inspectit.agent.analyzer.impl; + +import info.novatec.inspectit.agent.analyzer.IClassPoolAnalyzer; +import info.novatec.inspectit.agent.analyzer.IInheritanceAnalyzer; +import info.novatec.inspectit.agent.analyzer.IMatcher; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; + +import java.util.Iterator; +import java.util.List; + +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.NotFoundException; + +/** + * The super class matcher implementation is used to check if the class name of the configuration is + * equal to one of the super classes of the passed class. All of the calls to these methods are + * mainly delegated to either an {@link DirectMatcher} or an {@link IndirectMatcher}, depending if + * the configuration is virtual (contains a pattern). + * + * @author Patrice Bouillet + * + */ +public class SuperclassMatcher extends AbstractMatcher { + + /** + * The inheritance checker used to check if a super class matches. + */ + private final IInheritanceAnalyzer inheritanceAnalyzer; + + /** + * The {@link IMatcher} delegator object to route the calls of all methods to. + */ + private IMatcher delegateMatcher; + + /** + * The only constructor which needs a reference to the {@link UnregisteredSensorConfig} instance + * of the corresponding configuration. + * + * @param inheritanceAnalyzer + * The inheritance analyzer. + * @param classPoolAnalyzer + * The class pool analyzer. + * @param unregisteredSensorConfig + * The sensor configuration. + * @see AbstractMatcher + */ + public SuperclassMatcher(IInheritanceAnalyzer inheritanceAnalyzer, IClassPoolAnalyzer classPoolAnalyzer, UnregisteredSensorConfig unregisteredSensorConfig) { + super(classPoolAnalyzer, unregisteredSensorConfig); + + this.inheritanceAnalyzer = inheritanceAnalyzer; + + if (unregisteredSensorConfig.isVirtual()) { + delegateMatcher = new IndirectMatcher(classPoolAnalyzer, unregisteredSensorConfig); + } else { + delegateMatcher = new DirectMatcher(classPoolAnalyzer, unregisteredSensorConfig); + } + } + + /** + * {@inheritDoc} + */ + public boolean compareClassName(ClassLoader classLoader, String className) throws NotFoundException { + Iterator i = inheritanceAnalyzer.getSuperclassIterator(classLoader, className); + while (i.hasNext()) { + CtClass clazz = i.next(); + if (delegateMatcher.compareClassName(classLoader, clazz.getName())) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + public List getMatchingMethods(ClassLoader classLoader, String className) throws NotFoundException { + return delegateMatcher.getMatchingMethods(classLoader, className); + } + + /** + * {@inheritDoc} + */ + public List getMatchingConstructors(ClassLoader classLoader, String className) throws NotFoundException { + return delegateMatcher.getMatchingConstructors(classLoader, className); + } + + /** + * {@inheritDoc} + */ + public void checkParameters(List methods) throws NotFoundException { + delegateMatcher.checkParameters(methods); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/analyzer/impl/ThrowableMatcher.java b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/ThrowableMatcher.java new file mode 100644 index 000000000..2ac27c978 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/analyzer/impl/ThrowableMatcher.java @@ -0,0 +1,117 @@ +package info.novatec.inspectit.agent.analyzer.impl; + +import info.novatec.inspectit.agent.analyzer.IClassPoolAnalyzer; +import info.novatec.inspectit.agent.analyzer.IInheritanceAnalyzer; +import info.novatec.inspectit.agent.analyzer.IMatcher; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; + +import java.util.ArrayList; +import java.util.List; + +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.NotFoundException; + +/** + * The throwable matcher implementation is used to check if the class name of the configuration is a + * subclass of {@link Throwable}. All of the calls to these methods are delegated to either an + * {@link DirectMatcher}, an {@link IndirectMatcher}, an {@link SuperclassMatcher} or an + * {@link InterfaceMatcher}, depending on which matcher type is passed to the constructor. + * + * @author Eduard Tudenhoefner + * + */ +public class ThrowableMatcher extends AbstractMatcher { + + /** + * The inheritance checker used to check if a super class matches. + */ + private final IInheritanceAnalyzer inheritanceAnalyzer; + + /** + * The {@link IMatcher} delegator object to route the calls of all methods to. + */ + private IMatcher delegateMatcher; + + /** + * The only constructor which needs a reference to the {@link UnregisteredSensorConfig} instance + * of the corresponding configuration. + * + * @param inheritanceAnalyzer + * The inheritance analyzer. + * @param classPoolAnalyzer + * The class pool analyzer. + * @param unregisteredSensorConfig + * The sensor configuration. + * @param delegateMatcher + * The {@link IMatcher} delegator object to route the calls of all methods to. + * @see AbstractMatcher + */ + public ThrowableMatcher(IInheritanceAnalyzer inheritanceAnalyzer, IClassPoolAnalyzer classPoolAnalyzer, UnregisteredSensorConfig unregisteredSensorConfig, IMatcher delegateMatcher) { + super(classPoolAnalyzer, unregisteredSensorConfig); + + this.inheritanceAnalyzer = inheritanceAnalyzer; + this.delegateMatcher = delegateMatcher; + } + + /** + * {@inheritDoc} + */ + public boolean compareClassName(ClassLoader classLoader, String className) throws NotFoundException { + return delegateMatcher.compareClassName(classLoader, className); + } + + /** + * {@inheritDoc} + */ + public List getMatchingMethods(ClassLoader classLoader, String className) throws NotFoundException { + return delegateMatcher.getMatchingMethods(classLoader, className); + } + + /** + * {@inheritDoc} + */ + public List getMatchingConstructors(ClassLoader classLoader, String className) throws NotFoundException { + List matchingConstructors = delegateMatcher.getMatchingConstructors(classLoader, className); + + if (isThrowable(classLoader, className)) { + if (matchingConstructors.isEmpty()) { + matchingConstructors = new ArrayList(); + } + + // add the throwable constructors to the other constructors + CtClass clazz = classPoolAnalyzer.getClassPool(classLoader).get(className); + CtConstructor[] constructors = clazz.getConstructors(); + for (CtConstructor constructor : constructors) { + if (!matchingConstructors.contains(constructor)) { + matchingConstructors.add(constructor); + } + } + } + + return matchingConstructors; + } + + /** + * {@inheritDoc} + */ + public void checkParameters(List methods) throws NotFoundException { + delegateMatcher.checkParameters(methods); + } + + /** + * Checks whether the current class is of type {@link Throwable}. + * + * @param classLoader + * The class loader of the given class. + * @param className + * The name of the current class. + * @return True if the current class is of type {@link Throwable}, false otherwise. + */ + private boolean isThrowable(ClassLoader classLoader, String className) { + return inheritanceAnalyzer.subclassOf(className, "java.lang.Throwable", classPoolAnalyzer.getClassPool(classLoader)); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/buffer/AbstractBufferStrategy.java b/Agent/src/info/novatec/inspectit/agent/buffer/AbstractBufferStrategy.java new file mode 100644 index 000000000..96381e006 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/buffer/AbstractBufferStrategy.java @@ -0,0 +1,29 @@ +package info.novatec.inspectit.agent.buffer; + +import info.novatec.inspectit.agent.config.IConfigurationStorage; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Abstract class for all {@link IBufferStrategy} for correct initialization with Spring. + * + * @author Ivan Senic + * + * @param + */ +public abstract class AbstractBufferStrategy implements InitializingBean, IBufferStrategy { + + /** + * Configuration storage to read settings from. + */ + @Autowired + private IConfigurationStorage configurationStorage; + + /** + * {@inheritDoc} + */ + public void afterPropertiesSet() throws Exception { + this.init(configurationStorage.getBufferStrategyConfig().getSettings()); + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/buffer/IBufferStrategy.java b/Agent/src/info/novatec/inspectit/agent/buffer/IBufferStrategy.java new file mode 100644 index 000000000..585fda0a6 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/buffer/IBufferStrategy.java @@ -0,0 +1,35 @@ +package info.novatec.inspectit.agent.buffer; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * A BufferStrategy is used to define the behavior of the value objects once a connection problem + * appears. + * + * @param + * The element contained in the list. + * + * @author Patrice Bouillet + * + */ +public interface IBufferStrategy extends Iterator> { + + /** + * Adds a list of measurements. + * + * @param measurements + * The measurements to add. + */ + void addMeasurements(List measurements); + + /** + * Initializes the buffer strategy with the given {@link Map}. + * + * @param settings + * The settings as a {@link Map}. + */ + void init(Map settings); + +} diff --git a/Agent/src/info/novatec/inspectit/agent/buffer/impl/SimpleBufferStrategy.java b/Agent/src/info/novatec/inspectit/agent/buffer/impl/SimpleBufferStrategy.java new file mode 100644 index 000000000..dcd40979e --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/buffer/impl/SimpleBufferStrategy.java @@ -0,0 +1,95 @@ +package info.novatec.inspectit.agent.buffer.impl; + +import info.novatec.inspectit.agent.buffer.AbstractBufferStrategy; +import info.novatec.inspectit.agent.buffer.IBufferStrategy; +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.spring.logger.Log; + +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import org.slf4j.Logger; + +/** + * The simplest version of a buffer strategy contains just the reference to one measurement list. + * Every time a new one is added, the old one is thrown away. + * + * @author Patrice Bouillet + * + */ +public class SimpleBufferStrategy extends AbstractBufferStrategy implements IBufferStrategy { + + /** + * The logger of the class. + */ + @Log + Logger log; + + /** + * Stores the reference to the last given measurements. + */ + private List measurements; + + /** + * True if measurements were added and available. + */ + private volatile boolean newMeasurements = false; + + /** + * {@inheritDoc} + */ + public final void addMeasurements(final List measurements) { + if (null == measurements) { + throw new IllegalArgumentException("Measurements cannot be null!"); + } + synchronized (this) { + if (newMeasurements) { + // if the measurements already exist, this buffer strategy will simply drop the old + // ones, because we can not let the data pile up if the sending of the data is not + // fast enough + if (log.isDebugEnabled()) { + log.debug("Possible data loss due to the excessive data creation on the Agent!"); + } + } + this.measurements = measurements; + newMeasurements = true; + } + } + + /** + * {@inheritDoc} + */ + public final boolean hasNext() { + return newMeasurements; + } + + /** + * {@inheritDoc} + */ + public final List next() { + synchronized (this) { + if (newMeasurements) { + newMeasurements = false; + return measurements; + } + } + + throw new NoSuchElementException(); + } + + /** + * {@inheritDoc} + */ + public final void remove() { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + public final void init(final Map settings) { + // nothing to do + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/buffer/impl/SizeBufferStrategy.java b/Agent/src/info/novatec/inspectit/agent/buffer/impl/SizeBufferStrategy.java new file mode 100644 index 000000000..ee25714bc --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/buffer/impl/SizeBufferStrategy.java @@ -0,0 +1,118 @@ +package info.novatec.inspectit.agent.buffer.impl; + +import info.novatec.inspectit.agent.buffer.AbstractBufferStrategy; +import info.novatec.inspectit.agent.buffer.IBufferStrategy; +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.spring.logger.Log; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; + +/** + * This implementation will hold all list of measurements for the given size. It works as a FILO + * stack. + * + * @author Patrice Bouillet + * + */ +public class SizeBufferStrategy extends AbstractBufferStrategy implements IBufferStrategy { + + /** + * The logger of the class. + */ + @Log + Logger log; + + /** + * The default count if none is specified. + */ + private static final int DEFAULT_COUNT = 60; + + /** + * The linked list containing the FILO stack. + */ + private LinkedList> stack; // NOPMD + + /** + * The stack size. + */ + private int size; + + /** + * Delegates to the second constructor with the default count. + */ + public SizeBufferStrategy() { + this(DEFAULT_COUNT); + } + + /** + * The second constructor where one can specify the actual count or stack size. + * + * @param size + * The stack size. + */ + public SizeBufferStrategy(int size) { + this.size = size; + stack = new LinkedList>(); + } + + /** + * {@inheritDoc} + */ + public void addMeasurements(List measurements) { + if (null == measurements) { + throw new IllegalArgumentException("Measurements cannot be null!"); + } + + synchronized (this) { + // as we can only add one element at the time, we only have to delete + // the oldest element. + if (stack.size() >= size) { + // if the measurements stack size is reached, this buffer strategy will simply drop + // the old ones, because we can not let the data pile up if the sending of the data + // is not fast enough + stack.removeFirst(); + log.info("Possible data loss due to the excessive data creation on the Agent!"); + } + + stack.addLast(measurements); + } + + } + + /** + * {@inheritDoc} + */ + public boolean hasNext() { + return !stack.isEmpty(); + } + + /** + * {@inheritDoc} + */ + public List next() { + synchronized (this) { + return stack.removeLast(); + } + } + + /** + * {@inheritDoc} + */ + public void remove() { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + public void init(Map settings) { + if (settings.containsKey("size")) { + this.size = Integer.parseInt((String) settings.get("size")); + } + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/config/IConfigurationReader.java b/Agent/src/info/novatec/inspectit/agent/config/IConfigurationReader.java new file mode 100644 index 000000000..01a6103fc --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/IConfigurationReader.java @@ -0,0 +1,20 @@ +package info.novatec.inspectit.agent.config; + +/** + * Defines an interface for the configuration files. Every configuration syntax (plain text, XML, + * ...) should have its own implementation. + * + * @author Patrice Bouillet + * + */ +public interface IConfigurationReader { + + /** + * Loads the configuration of this reader from the underlying storage. + * + * @throws ParserException + * Thrown if there was an exception caught when loading the configuration. + */ + void load() throws ParserException; + +} diff --git a/Agent/src/info/novatec/inspectit/agent/config/IConfigurationStorage.java b/Agent/src/info/novatec/inspectit/agent/config/IConfigurationStorage.java new file mode 100644 index 000000000..2fa1da532 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/IConfigurationStorage.java @@ -0,0 +1,277 @@ +package info.novatec.inspectit.agent.config; + +import info.novatec.inspectit.agent.analyzer.IMatchPattern; +import info.novatec.inspectit.agent.analyzer.IMatcher; +import info.novatec.inspectit.agent.config.impl.MethodSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.PlatformSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.RepositoryConfig; +import info.novatec.inspectit.agent.config.impl.StrategyConfig; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; +import info.novatec.inspectit.agent.sensor.exception.IExceptionSensor; + +import java.util.List; +import java.util.Map; + +/** + * This storage is used by all configuration readers to store the information into. + * + * @author Patrice Bouillet + * @author Eduard Tudenhoefner + * + */ +public interface IConfigurationStorage { + + /** + * Sets the repository. Internally, a {@link RepositoryConfig} class is instantiated and filled + * with the proper arguments. + * + * @param host + * The host ip / name. + * @param port + * The host port. + * @throws StorageException + * Thrown if something with the host name or port is wrong. + */ + void setRepository(String host, int port) throws StorageException; + + /** + * Returns an instance of the {@link RepositoryConfig} class which is filled with the values by + * the {@link #setRepository(String, int)} method. + * + * @return The {@link RepositoryConfig} instance. + */ + RepositoryConfig getRepositoryConfig(); + + /** + * Sets the name of the Agent. + * + * @param name + * The name of the Agent to set. + * @throws StorageException + * Thrown if something with the agent name is wrong. + */ + void setAgentName(String name) throws StorageException; + + /** + * Returns the name of the Agent. + * + * @return The name of the Agent. + */ + String getAgentName(); + + /** + * Sets the unique buffer strategy. The parameters are stored in the {@link StrategyConfig} + * class. + * + * @param clazzName + * The fully qualified name of the buffer strategy class. + * @param settings + * A map containing some optional settings for the buffer. + * @throws StorageException + * This exception is thrown if something unexpected happens while initializing the + * buffer strategy. + */ + void setBufferStrategy(String clazzName, Map settings) throws StorageException; + + /** + * Returns a {@link StrategyConfig} instance containing the buffer strategy information. + * + * @return An instance of {@link StrategyConfig}. + */ + StrategyConfig getBufferStrategyConfig(); + + /** + * Adds a sending strategy. The parameters are stored in the {@link StrategyConfig} class. + * + * @param clazzName + * The fully qualified name of the sending strategy class. + * @param settings + * A map containing some optional settings for the sending strategy. + * @throws StorageException + * This exception is thrown if something unexpected happens while initializing the + * buffer strategy. + */ + void addSendingStrategy(String clazzName, Map settings) throws StorageException; + + /** + * Returns a {@link List} of {@link StrategyConfig} instances containing the sending strategy + * information. + * + * @return A {@link List} of {@link StrategyConfig} instances. + */ + List getSendingStrategyConfigs(); + + /** + * Creates and initializes a {@link MethodSensorTypeConfig} out of the given parameters. A + * sensor type is always unique, hence only one instance exists which is used by all installed + * sensors in the target application. + * + * @param sensorTypeName + * The name of the sensor type. + * @param sensorTypeClass + * The fully qualified definition of the sensor type which is instantiated via + * reflection. + * @param priority + * The priority of the sensor type. + * @param settings + * A map containing optional settings. + * @throws StorageException + * This exception is thrown if something unexpected happens while initializing the + * buffer strategy. + */ + void addMethodSensorType(String sensorTypeName, String sensorTypeClass, PriorityEnum priority, Map settings) throws StorageException; + + /** + * Returns a {@link List} of the {@link MethodSensorTypeConfig} classes. + * + * @return A {@link List} of {@link MethodSensorTypeConfig} classes. + */ + List getMethodSensorTypes(); + + /** + * Returns a {@link List} of {@link MethodSensorTypeConfig} classes. + * + * @return A {@link List} of {@link MethodSensorTypeConfig} classes. + */ + List getExceptionSensorTypes(); + + /** + * Creates and initializes a {@link MethodSensorTypeConfig} out of the given parameters. A + * sensor type is always unique, hence only one instance exists which is used by all installed + * sensors in the target application. + * + * @param sensorTypeClass + * the fully qualified definition of the sensor type which is instantiated via + * reflection. + * @param settings + * A map containing optional settings. + * @throws StorageException + * This exception is thrown if something unexpected happens while initializing the + * buffer strategy. + */ + void addExceptionSensorType(String sensorTypeClass, Map settings) throws StorageException; + + /** + * Adds a new parameter for the exception sensor definition. + * + * @param sensorTypeName + * The name of the sensor type. + * @param targetClassName + * The name of the target class. + * @param isVirtual + * Defines if the signature does not matter, hence all classes matching the + * targetClassName despite their signatures are instrumented. + * @param settings + * Additional and optional settings for this sensor definition as a {@link Map}. The + * key and value has to be defined as a standard {@link String}.
+ * Available are the keys superclass and interface with the value + * true or false. + * @throws StorageException + * This exception is thrown if something unexpected happens while initializing the + * buffer strategy. + */ + void addExceptionSensorTypeParameter(String sensorTypeName, String targetClassName, boolean isVirtual, Map settings) throws StorageException; + + /** + * Creates and initializes a {@link MethodSensorTypeConfig} out of the given parameters. A + * sensor type is always unique, hence only one instance exists which is used by all installed + * sensors in the target application. + * + * @param sensorTypeClass + * The fully qualified definition of the sensor type which is instantiated via + * reflection. + * @param settings + * A map containing optional settings. + * @throws StorageException + * This exception is thrown if something unexpected happens while initializing the + * buffer strategy. + */ + void addPlatformSensorType(String sensorTypeClass, Map settings) throws StorageException; + + /** + * Returns a {@link List} of the {@link PlatformSensorTypeConfig} classes. + * + * @return A {@link List} of {@link PlatformSensorTypeConfig} classes. + */ + List getPlatformSensorTypes(); + + /** + * Adds a new sensor definition. + * + * @param sensorTypeName + * The name of the sensor type. + * @param targetClassName + * The name of the target class. + * @param targetMethodName + * The name of the target method. + * @param parameterList + * The list of parameters of the target method. + * @param ignoreSignature + * Defines if the signature does not matter for the method, hence all methods + * matching the targetMethodName despite their signatures are + * instrumented. + * @param settings + * Additional and optional settings for this sensor definition as a {@link Map}. The + * key and value has to be defined as a standard {@link String}.
+ * Available are the keys superclass and interface with the value + * true or false. + * @throws StorageException + * This exception is thrown if something unexpected happens while initializing the + * buffer strategy. + */ + void addSensor(String sensorTypeName, String targetClassName, String targetMethodName, List parameterList, boolean ignoreSignature, Map settings) throws StorageException; + + /** + * Returns a {@link List} of the {@link UnregisteredSensorConfig} classes. + * + * @return A {@link List} of {@link UnregisteredSensorConfig} classes. + */ + List getUnregisteredSensorConfigs(); + + /** + * Returns whether the {@link IExceptionSensor} is activated. + * + * @return Whether the {@link IExceptionSensor} is activated. + */ + boolean isExceptionSensorActivated(); + + /** + * Activates or deactivates the instrumentation of enhanced exception events with try/catch. + * + * @param enhancedEvent + * Boolean indicating whether to activate or deactivate enhanced events. + */ + void setEnhancedExceptionSensorActivated(boolean enhancedEvent); + + /** + * Returns whether enhanced exception events are instrumented with try/catch. + * + * @return Whether enhanced exception events are instrumented. + */ + boolean isEnhancedExceptionSensorActivated(); + + /** + * Returns the patterns that denote the classes that should be ignored. + * + * @return Returns the patterns that denote the classes that should be ignored. + */ + List getIgnoreClassesPatterns(); + + /** + * Adds the ignore classes pattern to the {@link IConfigurationStorage}. + * + * @param patternString + * String that will be used as pattern for ignoring. + */ + void addIgnoreClassesPattern(String patternString); + + /** + * Returns the matcher that can be used to test if the ClassLoader class should be instrumented + * in the way that class loading is delegated if the class to be loaded is inspectIT class. + * + * @return Returns the matcher that can be used to test if the ClassLoader class should be + * instrumented in the way that class loading is delegated if the class to be loaded is + * inspectIT class. + */ + IMatcher getClassLoaderDelegationMatcher(); +} \ No newline at end of file diff --git a/Agent/src/info/novatec/inspectit/agent/config/IPropertyAccessor.java b/Agent/src/info/novatec/inspectit/agent/config/IPropertyAccessor.java new file mode 100644 index 000000000..faeaa55f7 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/IPropertyAccessor.java @@ -0,0 +1,56 @@ +package info.novatec.inspectit.agent.config; + +import info.novatec.inspectit.agent.config.impl.PropertyAccessor.PropertyPath; +import info.novatec.inspectit.agent.config.impl.PropertyAccessor.PropertyPathStart; +import info.novatec.inspectit.communication.data.ParameterContentData; + +import java.util.List; + +/** + * This interface defines methods to access the contents of the fields and method parameters of + * classes. + * + * @author Patrice Bouillet + * + */ +public interface IPropertyAccessor { + + /** + * Returns the content of the property. Either a field of a class will be accessed, a method + * parameter or the return value. + * + * @see PropertyPathStart + * @see PropertyPath + * + * @param propertyPathStart + * This parameter defines the start of the path. + * @param clazz + * The current instance or class object of the executed method. + * @param parameters + * The method parameters (can be null). + * @param returnValue + * The return value of the method. + * @return The {@link String} representation of the field or parameter followed by the path. + * @throws PropertyAccessException + * This exception is thrown whenever something unexpectedly happens while accessing + * a property. + */ + String getPropertyContent(PropertyPathStart propertyPathStart, Object clazz, Object[] parameters, Object returnValue) throws PropertyAccessException; + + /** + * Converts the list of property accessors {@link PropertyPathStart} into a list of + * {@link ParameterContentData}. + * + * @param propertyAccessorList + * The list of property accessors. + * @param clazz + * The class object. + * @param parameters + * The parameters. + * @param returnValue + * The return value of the method. + * @return The list of {@link ParameterContentData}. + */ + List getParameterContentData(List propertyAccessorList, Object clazz, Object[] parameters, Object returnValue); + +} \ No newline at end of file diff --git a/Agent/src/info/novatec/inspectit/agent/config/ParserException.java b/Agent/src/info/novatec/inspectit/agent/config/ParserException.java new file mode 100644 index 000000000..f908f97be --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/ParserException.java @@ -0,0 +1,47 @@ +package info.novatec.inspectit.agent.config; + +/** + * This exception is thrown whenever {@link IConfigurationReader} implementation tries to parse a + * given source and finds some errors in it. + * + * @author Patrice Bouillet + * + */ +public class ParserException extends Exception { + + /** + * Serial version UID. + */ + private static final long serialVersionUID = -7005044097962205063L; + + /** + * Constructs a new exception with the specified detail message. The cause is not initialized, + * and may subsequently be initialized by a call to {@link #initCause}. + * + * @param message + * The detail message. The detail message is saved for later retrieval by the + * {@link #getMessage()} method. + */ + public ParserException(String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and cause. + *

+ * Note that the detail message associated with cause is not automatically + * incorporated in this exception's detail message. + * + * @param message + * the detail message (which is saved for later retrieval by the + * {@link #getMessage()} method). + * @param cause + * The cause (which is saved for later retrieval by the {@link #getCause()} method). + * (A null value is permitted, and indicates that the cause is nonexistent + * or unknown.) + */ + public ParserException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/config/PriorityEnum.java b/Agent/src/info/novatec/inspectit/agent/config/PriorityEnum.java new file mode 100644 index 000000000..425e401d1 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/PriorityEnum.java @@ -0,0 +1,29 @@ +package info.novatec.inspectit.agent.config; + +/** + * Enumeration used for the priority system of the sensor types. + * + * @author Patrice Bouillet + * + */ +public enum PriorityEnum { + + /** The priority used by the invocation tracing system. */ + INVOC, + + /** Minimum priority. */ + MIN, + + /** Low priority. */ + LOW, + + /** Normal priority. */ + NORMAL, + + /** High priority. */ + HIGH, + + /** Maximum priority. */ + MAX; + +} diff --git a/Agent/src/info/novatec/inspectit/agent/config/PropertyAccessException.java b/Agent/src/info/novatec/inspectit/agent/config/PropertyAccessException.java new file mode 100644 index 000000000..0e119be5e --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/PropertyAccessException.java @@ -0,0 +1,48 @@ +package info.novatec.inspectit.agent.config; + +import info.novatec.inspectit.agent.config.impl.PropertyAccessor; + +/** + * This exception is thrown whenever something unexpected happens while accessing a property. + * + * @author Patrice Bouillet + * @see PropertyAccessor + */ +public class PropertyAccessException extends Exception { + + /** + * The serial version UID. + */ + private static final long serialVersionUID = -5939579158523517975L; + + /** + * Constructs a new exception with the specified detail message. The cause is not initialized, + * and may subsequently be initialized by a call to {@link #initCause}. + * + * @param message + * The detail message. The detail message is saved for later retrieval by the + * {@link #getMessage()} method. + */ + public PropertyAccessException(String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and cause. + *

+ * Note that the detail message associated with cause is not automatically + * incorporated in this exception's detail message. + * + * @param message + * the detail message (which is saved for later retrieval by the + * {@link #getMessage()} method). + * @param cause + * The cause (which is saved for later retrieval by the {@link #getCause()} method). + * (A null value is permitted, and indicates that the cause is nonexistent + * or unknown.) + */ + public PropertyAccessException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/config/StorageException.java b/Agent/src/info/novatec/inspectit/agent/config/StorageException.java new file mode 100644 index 000000000..0a0080000 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/StorageException.java @@ -0,0 +1,47 @@ +package info.novatec.inspectit.agent.config; + +/** + * This exception is thrown whenever something unexpected happens while trying to access/store/load + * something from the {@link IConfigurationStorage} implementation. + * + * @author Patrice Bouillet + * + */ +public class StorageException extends Exception { + + /** + * The serial version UID. + */ + private static final long serialVersionUID = -1644533648562152813L; + + /** + * Constructs a new exception with the specified detail message. The cause is not initialized, + * and may subsequently be initialized by a call to {@link #initCause}. + * + * @param message + * The detail message. The detail message is saved for later retrieval by the + * {@link #getMessage()} method. + */ + public StorageException(String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and cause. + *

+ * Note that the detail message associated with cause is not automatically + * incorporated in this exception's detail message. + * + * @param message + * the detail message (which is saved for later retrieval by the + * {@link #getMessage()} method). + * @param cause + * The cause (which is saved for later retrieval by the {@link #getCause()} method). + * (A null value is permitted, and indicates that the cause is nonexistent + * or unknown.) + */ + public StorageException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/config/impl/AbstractSensorConfig.java b/Agent/src/info/novatec/inspectit/agent/config/impl/AbstractSensorConfig.java new file mode 100644 index 000000000..1d75fe770 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/impl/AbstractSensorConfig.java @@ -0,0 +1,224 @@ +package info.novatec.inspectit.agent.config.impl; + +import info.novatec.inspectit.agent.config.impl.PropertyAccessor.PropertyPathStart; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * The abstract sensor configuration which is used by the registered and unregistered sensor + * configuration classes. + * + * @author Patrice Bouillet + * + */ +public abstract class AbstractSensorConfig { + + /** + * The name of the target class. + */ + private String targetPackageName; + + /** + * The name of the target class. + */ + private String targetClassName; + + /** + * The name of the target method. + */ + private String targetMethodName; + + /** + * The parameter types (as the fully qualified name) of the method. + */ + private List parameterTypes = new ArrayList(); + + /** + * Additional settings are stored in this map. + */ + private Map settings = new HashMap(); + + /** + * Defines if this sensor configuration contains one or many definitions for a property access + * (class field / method parameter) to save. + */ + private boolean propertyAccess = false; + + /** + * If propertyAccess is set to true, then this list contains at least one element. + * The contents is of type {@link PropertyPathStart}. + */ + private List propertyAccessorList = new CopyOnWriteArrayList(); + + /** + * If this config defines a constructor. + */ + private boolean constructor = false; + + /** + * Returns a map of the defined settings. + * + * @return The map of settings. + */ + public Map getSettings() { + return settings; + } + + /** + * The map of settings. Both, key and value, should be standard strings. + * + * @param settings + * The map of settings. + */ + public void setSettings(Map settings) { + this.settings = settings; + } + + /** + * Returns the package name. + * + * @return The package name. + */ + public String getTargetPackageName() { + return targetPackageName; + } + + /** + * Sets the package name. + * + * @param targetPackageName + * The package name to set. + */ + public void setTargetPackageName(String targetPackageName) { + this.targetPackageName = targetPackageName; + } + + /** + * Returns the class name as a name only string. + * + * @return The class name. Example: String + */ + public String getTargetClassName() { + return targetClassName; + } + + /** + * Returns the fully qualified class name. + * + * @return FQN of a class. Example: java.lang.String + */ + public String getQualifiedTargetClassName() { + return targetPackageName + '.' + targetClassName; + } + + /** + * Sets the class name. Has to be a fully qualified class name, example: + * java.lang.String + * + * @param targetClassName + * The target class name to set. + */ + public void setTargetClassName(String targetClassName) { + this.targetClassName = targetClassName; + } + + /** + * Returns the method name without the signature. + * + * @return The method name. + */ + public String getTargetMethodName() { + return targetMethodName; + } + + /** + * Sets the method name. Has to be without the signature. So a defined method in the config file + * as test(java.lang.String) has to be extracted as just test. + * + * @param targetMethodName + * The method name. + */ + public void setTargetMethodName(String targetMethodName) { + this.targetMethodName = targetMethodName; + } + + /** + * The parameter types or the signature of the method. Returns a {@link List} of {@link String} + * instances containing the fully qualified name of the classes. + * + * @return The {@link List} of parameter types. + */ + public List getParameterTypes() { + return parameterTypes; + } + + /** + * Sets the parameter types. The {@link List} contains just of {@link String} instances. + * + * @param parameterTypes + * The {@link List} of parameter types. + */ + public void setParameterTypes(List parameterTypes) { + this.parameterTypes = parameterTypes; + } + + /** + * If this configuration defines a property access. + * + * @return Returns true if a property access is defines. + */ + public boolean isPropertyAccess() { + return propertyAccess; + } + + /** + * Sets if this sensor configuration defines a property access. + * + * @param propertyAccess + * If this sensor configuration defines a property access. + */ + public void setPropertyAccess(boolean propertyAccess) { + this.propertyAccess = propertyAccess; + } + + /** + * Returns the {@link List} containing {@link PropertyPathStart} objects. Only contains + * something if {@link #isPropertyAccess()} returns true. + * + * @return The {@link List} of {@link PropertyPathStart} objects. + */ + public List getPropertyAccessorList() { + return propertyAccessorList; + } + + /** + * If this sensor config defines a constructor. + * + * @param isConstructor + * the isConstructor to set + */ + public void setConstructor(boolean isConstructor) { + this.constructor = isConstructor; + } + + /** + * Is this sensor config defining an constructor? + * + * @return the isConstructor + */ + public boolean isConstructor() { + return constructor; + } + + /** + * {@inheritDoc} + */ + public String toString() { + return targetClassName + "#" + targetMethodName + parameterTypes; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/config/impl/AbstractSensorTypeConfig.java b/Agent/src/info/novatec/inspectit/agent/config/impl/AbstractSensorTypeConfig.java new file mode 100644 index 000000000..9de32310c --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/impl/AbstractSensorTypeConfig.java @@ -0,0 +1,117 @@ +package info.novatec.inspectit.agent.config.impl; + +import info.novatec.inspectit.agent.sensor.ISensor; + +import java.util.HashMap; +import java.util.Map; + +/** + * Abstract sensor type configuration class which is used by the {@link MethodSensorTypeConfig} and + * the {@link PlatformSensorTypeConfig}. + * + * @author Patrice Bouillet + * @see MethodSensorTypeConfig + * @see PlatformSensorTypeConfig + * + */ +public abstract class AbstractSensorTypeConfig { + + /** + * The hash value of this sensor type. + */ + private long id = -1; + + /** + * The sensor type for this kind of sensor type configuration. + */ + private ISensor sensorType; + + /** + * The name of the class. + */ + private String className; + + /** + * Some additional parameters. + */ + private Map parameters = new HashMap(); + + /** + * Returns the id. + * + * @return The id. + */ + public long getId() { + return id; + } + + /** + * Set the id of this sensor type. + * + * @param id + * The id to set. + */ + public void setId(long id) { + this.id = id; + } + + /** + * Returns the sensor type of this configuration. + * + * @return Returns the sensor type. + */ + public ISensor getSensorType() { + return sensorType; + } + + /** + * Set the sensor type of this configuration. + * + * @param sensorType + * The sensor type. + */ + public void setSensorType(ISensor sensorType) { + this.sensorType = sensorType; + } + + /** + * Returns the class name of the sensor type as fully qualified. + * + * @return The class name. + */ + public String getClassName() { + return className; + } + + /** + * The class name has to be stored as fully qualified, example: java.lang.String. + * + * @param className + * The class name. + */ + public void setClassName(String className) { + this.className = className; + } + + /** + * Returns a {@link Map} of optional parameters. Is never null, but the size of the map could be + * 0. + * + * @return A map of parameters. + */ + public Map getParameters() { + return parameters; + } + + /** + * The {@link Map} of parameters stores additional information about the sensor type. Key and + * value should be both Strings. + * + * @param parameters + * The parameters. + */ + public void setParameters(Map parameters) { + this.parameters = parameters; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/config/impl/ConfigurationStorage.java b/Agent/src/info/novatec/inspectit/agent/config/impl/ConfigurationStorage.java new file mode 100644 index 000000000..9a575dd19 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/impl/ConfigurationStorage.java @@ -0,0 +1,749 @@ +package info.novatec.inspectit.agent.config.impl; + +import info.novatec.inspectit.agent.analyzer.IClassPoolAnalyzer; +import info.novatec.inspectit.agent.analyzer.IInheritanceAnalyzer; +import info.novatec.inspectit.agent.analyzer.IMatchPattern; +import info.novatec.inspectit.agent.analyzer.IMatcher; +import info.novatec.inspectit.agent.analyzer.impl.ModifierMatcher; +import info.novatec.inspectit.agent.analyzer.impl.SimpleMatchPattern; +import info.novatec.inspectit.agent.analyzer.impl.SuperclassMatcher; +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.config.PriorityEnum; +import info.novatec.inspectit.agent.config.StorageException; +import info.novatec.inspectit.agent.jrebel.JRebelUtil; +import info.novatec.inspectit.communication.data.ParameterContentType; +import info.novatec.inspectit.spring.logger.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import javassist.Modifier; + +import org.slf4j.Logger; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * The default configuration storage implementation which stores everything in the memory. + *

+ * TODO: Event mechanism is needed so that new definitions can be added and other components are + * notified that something has been added. + * + * @author Patrice Bouillet + * @author Eduard Tudenhoefner + * + */ +@Component +public class ConfigurationStorage implements IConfigurationStorage, InitializingBean { + + /** + * The logger of the class. + */ + @Log + Logger log; + + /** + * The name of the property for the repository IP. + */ + private static final String REPOSITORY_PROPERTY = "inspectit.repository"; + + /** + * The class pool analyzer. + */ + private final IClassPoolAnalyzer classPoolAnalyzer; + + /** + * The inheritance analyzer. + */ + private final IInheritanceAnalyzer inheritanceAnalyzer; + + /** + * The repository configuration is used to store the needed information to connect to a remote + * CMR. + */ + private RepositoryConfig repository; + + /** + * The name of the agent. + */ + private String agentName; + + /** + * The used buffer strategy. + */ + private StrategyConfig bufferStrategy; + + /** + * The list of sending strategies. Default size is set to 1 as it's unlikely that more than one + * is defined. + */ + private List sendingStrategies = new ArrayList(1); + + /** + * The default size of the method sensor type list. + */ + private static final int METHOD_LIST_SIZE = 10; + + /** + * The list of method sensor types. Contains objects of type {@link MethodSensorTypeConfig}. + */ + private List methodSensorTypes = new ArrayList(METHOD_LIST_SIZE); + + /** + * The default size of the platform sensor type list. + */ + private static final int PLATFORM_LIST_SIZE = 10; + + /** + * The list of platform sensor types. Contains objects of type {@link PlatformSensorTypeConfig}. + */ + private List platformSensorTypes = new ArrayList(PLATFORM_LIST_SIZE); + + /** + * A list containing all the sensor definitions from the configuration. + */ + private List unregisteredSensorConfigs = new ArrayList(); + + /** + * Indicates whether the exception sensor is activated or not. + */ + private boolean exceptionSensorActivated = false; + + /** + * Indicates whether try/catch instrumentation should be used to handle all exception events. + */ + private boolean enhancedExceptionSensorActivated = false; + + /** + * List of the ignore classes patterns. Classes matching these patterns should be ignored by the + * configuration. + */ + private List ignoreClassesPatterns = new ArrayList(); + + /** + * The matcher that can be used to test if the ClassLoader class should be instrumented in the + * way that class loading is delegated if the class to be loaded is inspectIT class. + */ + private IMatcher classLoaderDelegationMatcher; + + /** + * Default constructor which takes 2 parameter. + * + * @param classPoolAnalyzer + * The class pool analyzer used by the sensor configuration. + * @param inheritanceAnalyzer + * The inheritance analyzer used by the sensor configuration. + */ + @Autowired + public ConfigurationStorage(IClassPoolAnalyzer classPoolAnalyzer, IInheritanceAnalyzer inheritanceAnalyzer) { + this.classPoolAnalyzer = classPoolAnalyzer; + this.inheritanceAnalyzer = inheritanceAnalyzer; + } + + /** + * {@inheritDoc} + */ + public final void setRepository(String host, int port) throws StorageException { + if (null == host || "".equals(host)) { + throw new StorageException("Repository host name cannot be null or empty!"); + } + + if (port < 1) { + throw new StorageException("Repository port has to be greater than 0!"); + } + + // can not reset repository + if (null == repository) { + this.repository = new RepositoryConfig(host, port); + } + + if (log.isInfoEnabled()) { + log.info("Repository definition added. Host: " + host + " Port: " + port); + } + } + + /** + * {@inheritDoc} + */ + public RepositoryConfig getRepositoryConfig() { + return repository; + } + + /** + * {@inheritDoc} + */ + public final void setAgentName(String name) throws StorageException { + if (null == name || "".equals(name)) { + throw new StorageException("Agent name cannot be null or empty!"); + } + + // don't allow reseting + if (null == agentName) { + agentName = name; + } + + if (log.isInfoEnabled()) { + log.info("Agent name set to: " + name); + } + } + + /** + * {@inheritDoc} + */ + public String getAgentName() { + return agentName; + } + + /** + * {@inheritDoc} + */ + public void setBufferStrategy(String clazzName, Map settings) throws StorageException { + if (null == clazzName || "".equals(clazzName)) { + throw new StorageException("Buffer strategy class name cannot be null or empty!"); + } + + if (null == settings) { + settings = Collections.emptyMap(); + } + + this.bufferStrategy = new StrategyConfig(clazzName, settings); + + if (log.isDebugEnabled()) { + log.debug("Buffer strategy set to: " + clazzName); + } + } + + /** + * {@inheritDoc} + */ + public StrategyConfig getBufferStrategyConfig() { + return bufferStrategy; + } + + /** + * {@inheritDoc} + */ + public void addSendingStrategy(String clazzName, Map settings) throws StorageException { + if (null == clazzName || "".equals(clazzName)) { + throw new StorageException("Sending strategy class name cannot be null or empty!"); + } + + for (StrategyConfig config : sendingStrategies) { + if (clazzName.equals(config.getClazzName())) { + throw new StorageException("Sending strategy class is already registered!"); + } + } + + if (null == settings) { + settings = Collections.emptyMap(); + } + + sendingStrategies.add(new StrategyConfig(clazzName, settings)); + + if (log.isDebugEnabled()) { + log.debug("Sending strategy added: " + clazzName); + } + } + + /** + * {@inheritDoc} + */ + public List getSendingStrategyConfigs() { + return Collections.unmodifiableList(sendingStrategies); + } + + /** + * {@inheritDoc} + */ + public void addMethodSensorType(String sensorTypeName, String sensorTypeClass, PriorityEnum priority, Map settings) throws StorageException { + if (null == sensorTypeName || "".equals(sensorTypeName)) { + throw new StorageException("Method sensor type name cannot be null or empty!"); + } + + if (null == sensorTypeClass || "".equals(sensorTypeClass)) { + throw new StorageException("Method sensor type class name cannot be null or empty!"); + } + + if (null == priority) { + throw new StorageException("Method sensor type priority cannot be null!"); + } + + if (null == settings) { + settings = Collections.emptyMap(); + } + + MethodSensorTypeConfig sensorTypeConfig = new MethodSensorTypeConfig(); + sensorTypeConfig.setName(sensorTypeName); + sensorTypeConfig.setClassName(sensorTypeClass); + sensorTypeConfig.setPriority(priority); + sensorTypeConfig.setParameters(settings); + + methodSensorTypes.add(sensorTypeConfig); + + if (log.isDebugEnabled()) { + log.debug("Method sensor type added: " + sensorTypeName + " prio: " + priority); + } + } + + /** + * {@inheritDoc} + */ + public List getMethodSensorTypes() { + return Collections.unmodifiableList(methodSensorTypes); + } + + /** + * {@inheritDoc} + */ + public void addPlatformSensorType(String sensorTypeClass, Map settings) throws StorageException { + if (null == sensorTypeClass || "".equals(sensorTypeClass)) { + throw new StorageException("Platform sensor type class name cannot be null or empty!"); + } + + if (null == settings) { + settings = Collections.emptyMap(); + } + + PlatformSensorTypeConfig sensorTypeConfig = new PlatformSensorTypeConfig(); + sensorTypeConfig.setClassName(sensorTypeClass); + sensorTypeConfig.setParameters(settings); + + platformSensorTypes.add(sensorTypeConfig); + + if (log.isInfoEnabled()) { + log.info("Platform sensor type added: " + sensorTypeClass); + } + } + + /** + * {@inheritDoc} + */ + public List getPlatformSensorTypes() { + return Collections.unmodifiableList(platformSensorTypes); + } + + /** + * {@inheritDoc} + */ + public void addSensor(String sensorTypeName, String targetClassName, String targetMethodName, List parameterList, boolean ignoreSignature, Map settings) + throws StorageException { + if (null == sensorTypeName || "".equals(sensorTypeName)) { + throw new StorageException("Sensor type name for the sensor cannot be null or empty!"); + } + + if (null == targetClassName || "".equals(targetClassName)) { + throw new StorageException("Target class name cannot be null or empty!"); + } + + if (null == targetMethodName || "".equals(targetMethodName)) { + throw new StorageException("Target method name cannot be null or empty!"); + } + + if (null == settings) { + settings = Collections.emptyMap(); + } + + UnregisteredSensorConfig sensorConfig = new UnregisteredSensorConfig(classPoolAnalyzer, inheritanceAnalyzer); + sensorConfig.setTargetClassName(targetClassName); + sensorConfig.setTargetMethodName(targetMethodName); + if ("".equals(targetMethodName)) { + sensorConfig.setConstructor(true); + } + sensorConfig.setIgnoreSignature(ignoreSignature); + sensorConfig.setParameterTypes(parameterList); + sensorConfig.setSettings(settings); + + MethodSensorTypeConfig methodSensorTypeConfig = getMethodSensorTypeConfigForName(sensorTypeName); + sensorConfig.setSensorTypeConfig(methodSensorTypeConfig); + + // check for a virtual definition + if (ignoreSignature) { + sensorConfig.setVirtual(true); + } + + // check if we are dealing with a superclass definition + if (settings.containsKey("superclass") && settings.get("superclass").equals("true")) { + sensorConfig.setSuperclass(true); + } + + // check if we are dealing with a interface definition + if (settings.containsKey("interface") && settings.get("interface").equals("true")) { + sensorConfig.setInterface(true); + } + + // check for annotation + if (settings.containsKey("annotation")) { + sensorConfig.setAnnotationClassName((String) settings.get("annotation")); + } + + // check if the modifiers are set + if (settings.containsKey("modifiers")) { + String modifiersString = (String) settings.get("modifiers"); + String separator = ","; + StringTokenizer tokenizer = new StringTokenizer(modifiersString, separator); + int modifiers = 0; + while (tokenizer.hasMoreTokens()) { + String modifier = tokenizer.nextToken().trim(); + if (modifier != null && modifier.startsWith("pub")) { + modifiers |= Modifier.PUBLIC; + } else if (modifier != null && modifier.startsWith("priv")) { + modifiers |= Modifier.PRIVATE; + } else if (modifier != null && modifier.startsWith("prot")) { + modifiers |= Modifier.PROTECTED; + } else if (modifier != null && modifier.startsWith("def")) { + modifiers |= ModifierMatcher.DEFAULT; + } + } + sensorConfig.setModifiers(modifiers); + } + + if (settings.containsKey("field")) { + @SuppressWarnings("unchecked") + List fieldAccessorList = (List) settings.get("field"); + + for (String fieldDefinition : fieldAccessorList) { + String[] fieldDefinitionParts = fieldDefinition.split(";"); + String name = fieldDefinitionParts[0]; + PropertyAccessor.PropertyPathStart start = new PropertyAccessor.PropertyPathStart(); + start.setName(name); + start.setContentType(ParameterContentType.FIELD); + + String[] steps = fieldDefinitionParts[1].split("\\."); + PropertyAccessor.PropertyPath parentPath = start; + for (String step : steps) { + PropertyAccessor.PropertyPath path = new PropertyAccessor.PropertyPath(); + path.setName(step); + parentPath.setPathToContinue(path); + parentPath = path; + } + + sensorConfig.getPropertyAccessorList().add(start); + } + } + + if (settings.containsKey("property")) { + @SuppressWarnings("unchecked") + List propertyAccessorList = (List) settings.get("property"); + + for (String fieldDefinition : propertyAccessorList) { + String[] fieldDefinitionParts = fieldDefinition.split(";"); + int position = Integer.parseInt(fieldDefinitionParts[0]); + String name = fieldDefinitionParts[1]; + PropertyAccessor.PropertyPathStart start = new PropertyAccessor.PropertyPathStart(); + start.setName(name); + start.setContentType(ParameterContentType.PARAM); + start.setSignaturePosition(position); + + if (3 == fieldDefinitionParts.length) { + String[] steps = fieldDefinitionParts[2].split("\\."); + PropertyAccessor.PropertyPath parentPath = start; + for (String step : steps) { + PropertyAccessor.PropertyPath path = new PropertyAccessor.PropertyPath(); + path.setName(step); + parentPath.setPathToContinue(path); + parentPath = path; + } + } + + sensorConfig.getPropertyAccessorList().add(start); + } + } + + if (settings.containsKey("return") && !sensorConfig.isConstructor()) { + @SuppressWarnings("unchecked") + List returnAccessorList = (List) settings.get("return"); + + for (String returnDefinition : returnAccessorList) { + String[] returnDefinitionParts = returnDefinition.split(";"); + String name = returnDefinitionParts[0]; + PropertyAccessor.PropertyPathStart start = new PropertyAccessor.PropertyPathStart(); + start.setName(name); + start.setContentType(ParameterContentType.RETURN); + + if (returnDefinitionParts.length > 1) { + String[] steps = returnDefinitionParts[1].split("\\."); + PropertyAccessor.PropertyPath parentPath = start; + for (String step : steps) { + PropertyAccessor.PropertyPath path = new PropertyAccessor.PropertyPath(); + path.setName(step); + parentPath.setPathToContinue(path); + parentPath = path; + } + } + + sensorConfig.getPropertyAccessorList().add(start); + } + } + + sensorConfig.setPropertyAccess(!sensorConfig.getPropertyAccessorList().isEmpty()); + + sensorConfig.completeConfiguration(); + + unregisteredSensorConfigs.add(sensorConfig); + + if (log.isDebugEnabled()) { + log.debug("Sensor configuration added: " + sensorConfig.toString()); + } + + if (methodSensorTypeConfig.isJRebelActive()) { + UnregisteredSensorConfig jRebelSensorConfig = JRebelUtil.getJRebelSensorConfiguration(sensorConfig, classPoolAnalyzer, inheritanceAnalyzer); + jRebelSensorConfig.completeConfiguration(); + unregisteredSensorConfigs.add(jRebelSensorConfig); + + if (log.isDebugEnabled()) { + log.debug("Sensor configuration for JRebel enhanced classes added: " + jRebelSensorConfig.toString()); + } + } + } + + /** + * Returns the matching {@link MethodSensorTypeConfig} for the passed name. + * + * @param sensorTypeName + * The name to look for. + * @return The {@link MethodSensorTypeConfig} which name is equal to the passed sensor type name + * in the method parameter. + * @throws StorageException + * Throws the storage exception if no method sensor type configuration can be found. + */ + private MethodSensorTypeConfig getMethodSensorTypeConfigForName(String sensorTypeName) throws StorageException { + for (MethodSensorTypeConfig config : methodSensorTypes) { + if (config.getName().equals(sensorTypeName)) { + return config; + } + } + + throw new StorageException("Could not find method sensor type with name: " + sensorTypeName); + } + + /** + * Returns the matching {@link MethodSensorTypeConfig} of the Exception Sensor for the passed + * name. + * + * @param sensorTypeName + * The name to look for. + * @return The {@link MethodSensorTypeConfig} which name is equal to the passed sensor type name + * in the method parameter. + * @throws StorageException + * Throws the storage exception if no method sensor type configuration can be found. + */ + private MethodSensorTypeConfig getExceptionSensorTypeConfigForName(String sensorTypeName) throws StorageException { + for (MethodSensorTypeConfig config : methodSensorTypes) { + if (config.getName().equals(sensorTypeName)) { + return config; + } + } + + throw new StorageException("Could not find exception sensor type with name: " + sensorTypeName); + } + + /** + * {@inheritDoc} + */ + public List getUnregisteredSensorConfigs() { + return Collections.unmodifiableList(unregisteredSensorConfigs); + } + + /** + * {@inheritDoc} + */ + public List getExceptionSensorTypes() { + // TODO ET: could also be improved by adding the configs directly to exceptionSensorTypes + // when they are added to the methodSensorTypes + List exceptionSensorTypes = new ArrayList(); + for (MethodSensorTypeConfig config : methodSensorTypes) { + if (config.getName().equals("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor")) { + exceptionSensorTypes.add(config); + } + } + + return Collections.unmodifiableList(exceptionSensorTypes); + } + + /** + * {@inheritDoc} + */ + public void addExceptionSensorType(String sensorTypeClass, Map settings) throws StorageException { + if (null == sensorTypeClass || "".equals(sensorTypeClass)) { + throw new StorageException("Exception sensor type class name cannot be null or empty!"); + } + + if (null == settings) { + settings = Collections.emptyMap(); + } + + MethodSensorTypeConfig sensorTypeConfig = new MethodSensorTypeConfig(); + sensorTypeConfig.setName(sensorTypeClass); + sensorTypeConfig.setClassName(sensorTypeClass); + sensorTypeConfig.setParameters(settings); + + methodSensorTypes.add(sensorTypeConfig); + + if (log.isDebugEnabled()) { + log.debug("Exception sensor type added: " + sensorTypeClass); + } + } + + /** + * {@inheritDoc} + */ + public void addExceptionSensorTypeParameter(String sensorTypeName, String targetClassName, boolean isVirtual, Map settings) throws StorageException { + if (null == sensorTypeName || "".equals(sensorTypeName)) { + throw new StorageException("Sensor type name for the sensor cannot be null or empty!"); + } + + if (null == targetClassName || "".equals(targetClassName)) { + throw new StorageException("Target class name cannot be null or empty!"); + } + + if (null == settings) { + settings = Collections.emptyMap(); + } + + UnregisteredSensorConfig sensorConfig = new UnregisteredSensorConfig(classPoolAnalyzer, inheritanceAnalyzer); + + sensorConfig.setVirtual(isVirtual); + + // check if we are dealing with a superclass definition + if (settings.containsKey("superclass") && settings.get("superclass").equals("true")) { + sensorConfig.setSuperclass(true); + } + + // check if we are dealing with a interface definition + if (settings.containsKey("interface") && settings.get("interface").equals("true")) { + sensorConfig.setInterface(true); + } + + // Now set all the given parameters + sensorConfig.setTargetClassName(targetClassName); + sensorConfig.setSettings(settings); + sensorConfig.setSensorTypeConfig(getExceptionSensorTypeConfigForName(sensorTypeName)); + sensorConfig.setTargetMethodName(""); + sensorConfig.setConstructor(true); + sensorConfig.setExceptionSensorActivated(true); + sensorConfig.setIgnoreSignature(true); + sensorConfig.completeConfiguration(); + + unregisteredSensorConfigs.add(sensorConfig); + exceptionSensorActivated = true; + } + + /** + * {@inheritDoc} + */ + public boolean isExceptionSensorActivated() { + return exceptionSensorActivated; + } + + /** + * {@inheritDoc} + */ + public void setEnhancedExceptionSensorActivated(boolean isEnhanced) { + this.enhancedExceptionSensorActivated = isEnhanced; + if (log.isDebugEnabled()) { + if (isEnhanced) { + log.debug("Using enhanced exception sensor mode"); + } else { + log.debug("Using simple exception sensor mode"); + } + } + } + + /** + * {@inheritDoc} + */ + public boolean isEnhancedExceptionSensorActivated() { + return enhancedExceptionSensorActivated; + } + + /** + * {@inheritDoc} + */ + public List getIgnoreClassesPatterns() { + return ignoreClassesPatterns; + } + + /** + * {@inheritDoc} + */ + public void addIgnoreClassesPattern(String patternString) { + ignoreClassesPatterns.add(new SimpleMatchPattern(patternString)); + } + + /** + * {@inheritDoc} + */ + public IMatcher getClassLoaderDelegationMatcher() { + return classLoaderDelegationMatcher; + } + + /** + * {@inheritDoc} + */ + public void afterPropertiesSet() throws Exception { + loadConfigurationFromJvmParameters(); + createClassLoaderDelegationMatcher(); + } + + /** + * Checks if the JVM parameters have the repository and agent information. + */ + private void loadConfigurationFromJvmParameters() { + + // check if the information about the repository and agent is provided with the JVM params + String repositoryProperty = System.getProperty(REPOSITORY_PROPERTY); + + if (null == repositoryProperty) { + return; + } + + // expecting data in the form ip:port;name + StringTokenizer tokenizer = new StringTokenizer(repositoryProperty, ";"); + if (tokenizer.countTokens() == 2) { + // ip and host + String[] repositoryIpHost = tokenizer.nextToken().split(":"); + if (repositoryIpHost.length == 2) { + String repositoryIp = repositoryIpHost[0]; + String repositoryPort = repositoryIpHost[1]; + if (null != repositoryIp && !"".equals(repositoryIp) && null != repositoryPort && !"".equals(repositoryPort)) { + log.info("Repository information found in the JVM parameters: IP=" + repositoryIp + " Port=" + repositoryPort); + try { + int port = Integer.parseInt(repositoryPort); + setRepository(repositoryIp, port); + } catch (Exception e) { + log.warn("Repository could not be defined from the data in the JVM parameters", e); + } + } + } + + // agent + String agentName = tokenizer.nextToken(); + if (null != agentName && !"".equals(agentName)) { + try { + log.info("Agent name found in the JVM parameters: AgentName=" + agentName); + setAgentName(agentName); + } catch (Exception e) { + log.warn("Agent name could not be defined from the data in the JVM parameters", e); + } + } + } + } + + /** + * Creates the {@link #classLoaderDelegationMatcher}. + */ + private void createClassLoaderDelegationMatcher() { + UnregisteredSensorConfig fakeSensorConfig = new UnregisteredSensorConfig(classPoolAnalyzer, inheritanceAnalyzer); + fakeSensorConfig.setSuperclass(true); + fakeSensorConfig.setTargetClassName("java.lang.ClassLoader"); + fakeSensorConfig.setTargetMethodName("loadClass"); + fakeSensorConfig.setParameterTypes(Collections.singletonList("java.lang.String")); + fakeSensorConfig.setModifiers(Modifier.PUBLIC); + this.classLoaderDelegationMatcher = new SuperclassMatcher(inheritanceAnalyzer, classPoolAnalyzer, fakeSensorConfig); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/config/impl/FileConfigurationReader.java b/Agent/src/info/novatec/inspectit/agent/config/impl/FileConfigurationReader.java new file mode 100644 index 000000000..244bcdb0d --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/impl/FileConfigurationReader.java @@ -0,0 +1,585 @@ +package info.novatec.inspectit.agent.config.impl; + +import info.novatec.inspectit.agent.analyzer.IMatchPattern; +import info.novatec.inspectit.agent.config.IConfigurationReader; +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.config.ParserException; +import info.novatec.inspectit.agent.config.PriorityEnum; +import info.novatec.inspectit.agent.config.StorageException; +import info.novatec.inspectit.agent.logback.LogInitializer; +import info.novatec.inspectit.spring.logger.Log; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.slf4j.Logger; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * This config reader class reads simple config files. Simple in the way as you don't need any + * additional java libraries and every statement is in one line. + * + * @author Patrice Bouillet + * + */ +@Component("configurationReader") +public class FileConfigurationReader implements IConfigurationReader, InitializingBean { + + /** + * The logger of the class. + */ + @Log + Logger log; + + /** + * Default ignore classes patterns. These will be used if no patterns is supplied by the user. + */ + private static final String[] DEFAULT_IGNORE_PATTERNS = new String[] { "java.security.SecureClassLoader", "info.novatec.inspectit.*", "$Proxy*", "sun.*", "java.lang.ThreadLocal", + "java.lang.ref.Reference", "*_WLStub", "*[]" }; + + /** + * The configuration storage implementation. Used to store the information to. + */ + private final IConfigurationStorage configurationStorage; + + /** + * The name of the property (directory) of the configuration. + */ + private static final String CONFIGURATION_PROPERTY = "inspectit.config"; + + /** + * The name of the configuration file. + */ + private static final String CONFIGURATION_FILE = "inspectit-agent.cfg"; + + // The available strings to look for a valid beginning + /** Starting characters to mark the line a comment. */ + private static final String CONFIG_COMMENT = "#"; + /** Keyword to mark the repository definition. */ + private static final String CONFIG_REPOSITORY = "repository"; + /** Keyword to mark the send strategy definition. */ + private static final String CONFIG_SEND_STRATEGY = "send-strategy"; + /** Keyword to mark the buffer strategy definition. */ + private static final String CONFIG_BUFFER_STRATEGY = "buffer-strategy"; + /** Keyword to mark the method sensor definition. */ + private static final String CONFIG_METHOD_SENSOR_TYPE = "method-sensor-type"; + /** Keyword to mark the platform sensor definition. */ + private static final String CONFIG_PLATFORM_SENSOR_TYPE = "platform-sensor-type"; + /** Keyword to mark the assignment of a sensor. */ + private static final String CONFIG_SENSOR = "sensor"; + /** Keyword to configure the exception sensor. */ + private static final String CONFIG_EXCEPTION_SENSOR = "exception-sensor"; + /** Keyword to define the exception sensor type. */ + private static final String CONFIG_EXCEPTION_SENSOR_TYPE = "exception-sensor-type"; + /** Keyword to include additional configuration files. */ + private static final String CONFIG_INCLUDE_FILE = "$include"; + /** Keyword to exclude certain classes from instrumentation. */ + private static final String CONFIG_EXCLUDE_CLASS = "exclude-class"; + + /** + * Regular expression pattern to find the signatures of the method definitions in the + * configuration file. Pre-compiled for faster execution. + */ + private final Pattern methodSignature = Pattern.compile(".*\\((.+)\\)"); + + /** + * Regular expression pattern to find empty signatures -> '()' . Pre-compiled for faster + * execution. + */ + private final Pattern emptyMethodSignature = Pattern.compile(".*\\(\\)"); + + /** + * Default constructor which accepts one parameter. + * + * @param configurationStorage + * The configuration storage implementation. + */ + @Autowired + public FileConfigurationReader(IConfigurationStorage configurationStorage) { + this.configurationStorage = configurationStorage; + } + + /** + * The default location for this reader is the path in the system variable 'inspectit.config'. + *

+ * {@inheritDoc} + */ + public void load() throws ParserException { + String pathToConfig = System.getProperty(CONFIGURATION_PROPERTY) + File.separator + CONFIGURATION_FILE; + + // Fallback to the standard location of the inspectit configuration + // file when no system property is specified. + if ("".equals(pathToConfig)) { + pathToConfig = System.getProperty("user.dir") + File.separator + "inspectit" + File.separator + CONFIGURATION_PROPERTY; + } + + // Load and parse the file + try { + File configFile = new File(pathToConfig); + if (log.isDebugEnabled()) { + log.debug("Agent Configuration file found at: " + configFile.getAbsolutePath()); + } + InputStream is = new FileInputStream(configFile); + InputStreamReader reader = new InputStreamReader(is); + this.parse(reader, pathToConfig); + + // check if the exclude class patterns were supplied + // if not add the default ones to the configuration + List ignorePatterns = configurationStorage.getIgnoreClassesPatterns(); + if (null == ignorePatterns || ignorePatterns.isEmpty()) { + for (String ignorePatternString : DEFAULT_IGNORE_PATTERNS) { + configurationStorage.addIgnoreClassesPattern(ignorePatternString); + } + } + + } catch (FileNotFoundException e) { + log.info("Agent Configuration file not found at " + pathToConfig + ", aborting!"); + throw new ParserException("Agent Configuration file not found at " + pathToConfig, e); + } + } + + /** + * Parses the given file. + * + * @param reader + * The reader to open and parse. + * @param pathToConfig + * The path to the configuration file. + * @throws ParserException + * Thrown if there was an exception caught by parsing the config file. + */ + void parse(Reader reader, String pathToConfig) throws ParserException { + // check for a valid Reader object + if (null == reader) { + throw new ParserException("Input is null! Aborting parsing."); + } + + BufferedReader br = new BufferedReader(reader); + + String line = null; + try { + while ((line = br.readLine()) != null) { // NOPMD + // Skip empty and comment lines + if (line.trim().equals("") || line.startsWith(CONFIG_COMMENT)) { + continue; + } + + // Split the line into tokens + StringTokenizer tokenizer = new StringTokenizer(line, " "); + String discriminator = tokenizer.nextToken(); + + // check for the repository + if (discriminator.equalsIgnoreCase(CONFIG_REPOSITORY)) { + processRepositoryLine(tokenizer); + continue; + } + + // check for a sending strategy + if (discriminator.equalsIgnoreCase(CONFIG_SEND_STRATEGY)) { + processSendStrategyLine(tokenizer); + continue; + } + + // check for a buffer strategy + if (discriminator.equalsIgnoreCase(CONFIG_BUFFER_STRATEGY)) { + processBufferStrategyLine(tokenizer); + continue; + } + + // Check for the method sensor type + if (discriminator.equalsIgnoreCase(CONFIG_METHOD_SENSOR_TYPE)) { + processMethodSensorTypeLine(tokenizer); + continue; + } + + // Check for the platform sensor type + if (discriminator.equalsIgnoreCase(CONFIG_PLATFORM_SENSOR_TYPE)) { + processPlatformSensorTypeLine(tokenizer); + continue; + } + + // check for exception sensor type line + if (discriminator.equalsIgnoreCase(CONFIG_EXCEPTION_SENSOR_TYPE)) { + processExceptionSensorTypeLine(tokenizer); + continue; + } + + // check for exception sensor line + if (discriminator.equalsIgnoreCase(CONFIG_EXCEPTION_SENSOR)) { + processExceptionSensorLine(tokenizer); + continue; + } + + // check for a sensor + if (discriminator.equalsIgnoreCase(CONFIG_SENSOR)) { + processSensorLine(tokenizer); + continue; + } + + // check for a file include + if (discriminator.equalsIgnoreCase(CONFIG_INCLUDE_FILE)) { + processIncludeFileLine(tokenizer, pathToConfig); + continue; + } + + // check for exclude classes + if (discriminator.equalsIgnoreCase(CONFIG_EXCLUDE_CLASS)) { + processExcludeClassLine(tokenizer); + } + } + } catch (Throwable throwable) { // NOPMD + log.error("Error reading config on line : " + line); + throw new ParserException("Error reading config on line : " + line, throwable); + } + } + + /** + * Process the exception sensor type line. + * + * @param tokenizer + * {@link StringTokenizer} holding rest of the line. + * @throws ParserException + * If Exception Sensor Type can not be added to the configuration storage. + */ + private void processExceptionSensorTypeLine(StringTokenizer tokenizer) throws ParserException { + String sensorTypeClass = tokenizer.nextToken(); + + Map settings = new HashMap(); + while (tokenizer.hasMoreTokens()) { + String parameterToken = tokenizer.nextToken(); + StringTokenizer parameterTokenizer = new StringTokenizer(parameterToken, "="); + String leftSide = parameterTokenizer.nextToken(); + String rightSide = parameterTokenizer.nextToken(); + settings.put(leftSide, rightSide); + } + + Object mode = settings.get("mode"); + if (null != mode && "enhanced".equals(mode)) { + configurationStorage.setEnhancedExceptionSensorActivated(true); + } else { + configurationStorage.setEnhancedExceptionSensorActivated(false); + } + + try { + configurationStorage.addExceptionSensorType(sensorTypeClass, settings); + } catch (StorageException e) { + throw new ParserException("Could not add the exception sensor type to the storage", e); + } + } + + /** + * Processes an exception sensor line. + * + * @param tokenizer + * The tokenizer which contains the strings to create a sensor type. + * @throws ParserException + * Thrown if there was an exception caught by parsing the config file. + */ + private void processExceptionSensorLine(StringTokenizer tokenizer) throws ParserException { + // the sensor name is hardcoded here, because we don't define the + // fully-qualified name of the exception sensor in the config file. + String sensorTypeClass = "info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"; + String targetClassName = tokenizer.nextToken(); + boolean isVirtual = false; + + if (targetClassName.indexOf('*') > -1) { + isVirtual = true; + } + + Map settings = new HashMap(); + while (tokenizer.hasMoreTokens()) { + String parameterToken = tokenizer.nextToken(); + StringTokenizer parameterTokenizer = new StringTokenizer(parameterToken, "="); + String leftSide = parameterTokenizer.nextToken(); + String rightSide = parameterTokenizer.nextToken(); + settings.put(leftSide, rightSide); + } + + try { + configurationStorage.addExceptionSensorTypeParameter(sensorTypeClass, targetClassName, isVirtual, settings); + } catch (StorageException e) { + throw new ParserException("Could not add the exception sensor type parameter to the storage", e); + } + } + + /** + * Processes a method sensor type line. + * + * @param tokenizer + * The tokenizer which contains the strings to create a sensor type. + * @throws ParserException + * Thrown if there was an exception caught by parsing the config file. + */ + private void processMethodSensorTypeLine(StringTokenizer tokenizer) throws ParserException { + String sensorTypeName = tokenizer.nextToken(); + String sensorTypeClass = tokenizer.nextToken(); + String priorityString = tokenizer.nextToken(); + PriorityEnum priority = PriorityEnum.valueOf(priorityString); + + Map settings = new HashMap(); + while (tokenizer.hasMoreTokens()) { + String parameterToken = tokenizer.nextToken(); + StringTokenizer parameterTokenizer = new StringTokenizer(parameterToken, "="); + String leftSide = parameterTokenizer.nextToken(); + String rightSide = parameterTokenizer.nextToken(); + settings.put(leftSide, rightSide); + } + + try { + configurationStorage.addMethodSensorType(sensorTypeName, sensorTypeClass, priority, settings); + } catch (StorageException e) { + throw new ParserException("Could not add the method sensor type to the storage", e); + } + } + + /** + * Processes a platform sensor type line. + * + * @param tokenizer + * The tokenizer which contains the strings to create a sensor type. + * @throws ParserException + * Thrown if there was an exception caught by parsing the config file. + */ + private void processPlatformSensorTypeLine(StringTokenizer tokenizer) throws ParserException { + String sensorTypeClass = tokenizer.nextToken(); + + Map settings = new HashMap(); + while (tokenizer.hasMoreTokens()) { + String parameterToken = tokenizer.nextToken(); + StringTokenizer parameterTokenizer = new StringTokenizer(parameterToken, "="); + String leftSide = parameterTokenizer.nextToken(); + String rightSide = parameterTokenizer.nextToken(); + settings.put(leftSide, rightSide); + } + try { + configurationStorage.addPlatformSensorType(sensorTypeClass, settings); + } catch (StorageException e) { + throw new ParserException("Could not add the platform sensor type to the storage", e); + } + } + + /** + * Processes a sensor line. + * + * @param tokenizer + * The tokenizer which contains the strings to create a sensor. + * @throws ParserException + * Thrown if there was an exception caught by parsing the config file. + */ + private void processSensorLine(StringTokenizer tokenizer) throws ParserException { + String sensorTypeName = tokenizer.nextToken(); + String targetClassName = tokenizer.nextToken(); + String targetMethodName = tokenizer.nextToken(); + boolean ignoreSignature = false; + + // Trying to match the parameter types (if there are any) + Matcher m = methodSignature.matcher(targetMethodName); + List parameterList = Collections.emptyList(); + if (m.matches()) { + String[] classes = m.group(1).split(","); + parameterList = new ArrayList(classes.length); + + for (String clazz : classes) { + parameterList.add(clazz.trim()); + } + targetMethodName = targetMethodName.split("\\(")[0]; + } else if (emptyMethodSignature.matcher(targetMethodName).matches()) { + targetMethodName = targetMethodName.replaceAll("\\(\\)", ""); + } else { + ignoreSignature = true; + } + + Map settings = new HashMap(); + while (tokenizer.hasMoreTokens()) { + String parameterToken = tokenizer.nextToken(); + if (parameterToken.charAt(0) == '@') { + settings.put("annotation", parameterToken.substring(1)); + } else { + StringTokenizer parameterTokenizer = new StringTokenizer(parameterToken, "="); + String leftSide = parameterTokenizer.nextToken(); + String rightSide = parameterTokenizer.nextToken(); + + if ("property".equals(leftSide) || "p".equals(leftSide)) { + @SuppressWarnings("unchecked") + List propertyAccessorList = (List) settings.get("property"); + if (null == propertyAccessorList) { + propertyAccessorList = new ArrayList(); + settings.put("property", propertyAccessorList); + } + propertyAccessorList.add(rightSide); + } else if ("field".equals(leftSide) || "f".equals(leftSide)) { + @SuppressWarnings("unchecked") + List propertyAccessorList = (List) settings.get("field"); + if (null == propertyAccessorList) { + propertyAccessorList = new ArrayList(); + settings.put("field", propertyAccessorList); + } + propertyAccessorList.add(rightSide); + } else if ("return".equals(leftSide) || "r".equals(leftSide)) { + @SuppressWarnings("unchecked") + List propertyAccessorList = (List) settings.get("return"); + if (null == propertyAccessorList) { + propertyAccessorList = new ArrayList(); + settings.put("return", propertyAccessorList); + } + propertyAccessorList.add(rightSide); + } else { + settings.put(leftSide, rightSide); + } + } + } + + try { + configurationStorage.addSensor(sensorTypeName, targetClassName, targetMethodName, parameterList, ignoreSignature, settings); + } catch (StorageException e) { + throw new ParserException("Could not add the sensor to the storage", e); + } + } + + /** + * Processes a repository line and initializes it afterwards. + * + * @param tokenizer + * The string tokenizer which contains the definition of the repository. + * @throws ParserException + * Thrown if there was an exception caught by parsing the config file. + */ + private void processRepositoryLine(StringTokenizer tokenizer) throws ParserException { + String host = tokenizer.nextToken(); + int port = Integer.parseInt(tokenizer.nextToken()); + String name = tokenizer.nextToken(); + + // when we know the name init the logging + LogInitializer.setAgentNameAndInitLogging(name); + + try { + configurationStorage.setAgentName(name); + configurationStorage.setRepository(host, port); + } catch (StorageException e) { + throw new ParserException("Could net set the agent name or repository", e); + } + } + + /** + * Processes a send strategy line. + * + * @param tokenizer + * The tokenizer which contains the strings to create a sending strategy. + * @throws ParserException + * Thrown if there was an exception caught by parsing the config file. + */ + private void processSendStrategyLine(StringTokenizer tokenizer) throws ParserException { + String sendStrategyClass = tokenizer.nextToken(); + + Map settings = new HashMap(); + while (tokenizer.hasMoreTokens()) { + String parameterToken = tokenizer.nextToken(); + StringTokenizer parameterTokenizer = new StringTokenizer(parameterToken, "="); + String leftSide = parameterTokenizer.nextToken(); + String rightSide = parameterTokenizer.nextToken(); + settings.put(leftSide, rightSide); + } + try { + configurationStorage.addSendingStrategy(sendStrategyClass, settings); + } catch (StorageException e) { + throw new ParserException("Could not add the sending strategy to the storage", e); + } + } + + /** + * Processes a buffer strategy line. + * + * @param tokenizer + * The tokenizer which contains the strings to create a buffer strategy. + * @throws ParserException + * Thrown if there was an exception caught by parsing the config file. + */ + private void processBufferStrategyLine(StringTokenizer tokenizer) throws ParserException { + String bufferStrategyClass = tokenizer.nextToken(); + + Map settings = new HashMap(); + while (tokenizer.hasMoreTokens()) { + String parameterToken = tokenizer.nextToken(); + StringTokenizer parameterTokenizer = new StringTokenizer(parameterToken, "="); + String leftSide = parameterTokenizer.nextToken(); + String rightSide = parameterTokenizer.nextToken(); + settings.put(leftSide, rightSide); + } + try { + configurationStorage.setBufferStrategy(bufferStrategyClass, settings); + } catch (StorageException e) { + throw new ParserException("Could not set the buffer strategy in the storage", e); + } + } + + /** + * Process an additional configuration file. + * + * @param tokenizer + * The tokenizer which contains the path to an additional configuration file. + * @param pathToParentFile + * path to the parent file. + * @throws ParserException + * Thrown if there was an exception caught by parsing the config file. + */ + private void processIncludeFileLine(StringTokenizer tokenizer, String pathToParentFile) throws ParserException { + String fileName = tokenizer.nextToken(); + + File file = new File(fileName); + if (!file.isAbsolute()) { + // if the file does not denote an absolute file, we have to prepend + // the folder in which the current configuration is loaded. + file = new File(new File(pathToParentFile).getParent() + File.separator + fileName); + } + if (file.isDirectory()) { + log.info("Specified additional configuration is a folder: " + file.getAbsolutePath() + ", aborting!"); + throw new ParserException("Specified additional configuration is a folder: " + file.getAbsolutePath()); + } + + try { + log.info("Additional agent configuration file found at: " + file.getAbsolutePath()); + InputStream is = new FileInputStream(file); + InputStreamReader reader = new InputStreamReader(is); + this.parse(reader, file.getAbsolutePath()); + } catch (FileNotFoundException e) { + log.info("Additional agent configuration file not found at " + file.getAbsolutePath() + ", aborting!"); + throw new ParserException("Additional agent Configuration file not found at " + file.getAbsolutePath(), e); + } + } + + /** + * Process a line for the exclude classes configuration. + * + * @param tokenizer + * The tokenizer which contains the path to an additional configuration file. + */ + private void processExcludeClassLine(StringTokenizer tokenizer) { + while (tokenizer.hasMoreTokens()) { + String patternString = tokenizer.nextToken(); + configurationStorage.addIgnoreClassesPattern(patternString); + } + } + + /** + * {@inheritDoc} + */ + public void afterPropertiesSet() throws Exception { + load(); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/config/impl/MethodSensorTypeConfig.java b/Agent/src/info/novatec/inspectit/agent/config/impl/MethodSensorTypeConfig.java new file mode 100644 index 000000000..0c2ae8718 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/impl/MethodSensorTypeConfig.java @@ -0,0 +1,91 @@ +package info.novatec.inspectit.agent.config.impl; + +import info.novatec.inspectit.agent.config.PriorityEnum; + +import org.apache.commons.collections.MapUtils; + +/** + * Container for the values of a sensor type configuration. stores all the values defined in a + * config file for later access. + * + * @author Patrice Bouillet + */ +public class MethodSensorTypeConfig extends AbstractSensorTypeConfig { + + /** + * The name of the sensor type. + */ + private String name; + + /** + * The priority of this sensor type. The default is NORMAL. + */ + private PriorityEnum priority = PriorityEnum.NORMAL; + + /** + * Returns the unique name of the sensor type. + * + * @return The name of the sensor type. + */ + public String getName() { + return name; + } + + /** + * Sets the unique name of the sensor type. + * + * @param name + * The sensor name. + */ + public void setName(final String name) { + this.name = name; + } + + /** + * Returns the priority of this sensor type. Important for time or memory sensors. Can return + * one of:
+ * {@link PriorityEnum#MAX}
+ * {@link PriorityEnum#HIGH}
+ * {@link PriorityEnum#NORMAL}
+ * {@link PriorityEnum#LOW}
+ * {@link PriorityEnum#MIN}
+ * + * @return The priority of the sensor type. + */ + public PriorityEnum getPriority() { + return priority; + } + + /** + * Sets the priority of this sensor type. + * + * @param priority + * The priority. + */ + public void setPriority(PriorityEnum priority) { + this.priority = priority; + } + + /** + * {@inheritDoc} + */ + public String toString() { + return getId() + " :: name: " + name + " (" + priority + ")"; + } + + /** + * Returns if jRebel property is activated on the sensor. + * + * @return Returns if jRebel property is activated on the sensor. + */ + public boolean isJRebelActive() { + if (MapUtils.isNotEmpty(getParameters())) { + Object jRebelValue = getParameters().get("jRebel"); + if ("true".equals(jRebelValue)) { + return true; + } + } + return false; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/config/impl/PlatformSensorTypeConfig.java b/Agent/src/info/novatec/inspectit/agent/config/impl/PlatformSensorTypeConfig.java new file mode 100644 index 000000000..fe1c89205 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/impl/PlatformSensorTypeConfig.java @@ -0,0 +1,19 @@ +package info.novatec.inspectit.agent.config.impl; + +/** + * Only a marker class currently to define the platform sensor type configurations. They have no + * additional information as already available in the {@link AbstractSensorTypeConfig}. + * + * @author Patrice Bouillet + * + */ +public class PlatformSensorTypeConfig extends AbstractSensorTypeConfig { + + /** + * {@inheritDoc} + */ + public String toString() { + return getId() + " :: class: " + getClassName(); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/config/impl/PropertyAccessor.java b/Agent/src/info/novatec/inspectit/agent/config/impl/PropertyAccessor.java new file mode 100644 index 000000000..ef38e4e1b --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/impl/PropertyAccessor.java @@ -0,0 +1,411 @@ +package info.novatec.inspectit.agent.config.impl; + +import info.novatec.inspectit.agent.config.IPropertyAccessor; +import info.novatec.inspectit.agent.config.PropertyAccessException; +import info.novatec.inspectit.communication.data.ParameterContentData; +import info.novatec.inspectit.communication.data.ParameterContentType; +import info.novatec.inspectit.spring.logger.Log; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.slf4j.Logger; +import org.springframework.stereotype.Component; + +/** + * This class is used to programmatically build the path to access a specific method parameter or a + * field of a class. + * + * @author Patrice Bouillet + * @author Stefan Siegl + * + */ +@Component +public class PropertyAccessor implements IPropertyAccessor { + + /** + * The logger of this class. + */ + @Log + Logger log; + + /** + * Static null value for return value capturing in case the returned value was null + * . + */ + private static final String NULL_VALUE = "null"; + + /** + * An array containing the names of all methods that might be called by the PropertyAccessor. + * Names should not include the brackets. + */ + private static final String[] ALLOWED_METHODS = new String[] { "size", "length" }; + + /** + * {@inheritDoc} + */ + public String getPropertyContent(PropertyPathStart propertyPathStart, Object clazz, Object[] parameters, Object returnValue) throws PropertyAccessException { + if (null == propertyPathStart) { + throw new PropertyAccessException("Property path start cannot be null!"); + } + + if (null == propertyPathStart.contentType) { + throw new PropertyAccessException("Content type is not defined."); + } + + switch (propertyPathStart.contentType) { + case FIELD: + if (null == clazz) { + throw new PropertyAccessException("Class reference cannot be null!"); + } + return getPropertyContent(propertyPathStart.getPathToContinue(), clazz); + case PARAM: + if (null == parameters) { + throw new PropertyAccessException("Parameter array reference cannot be null!"); + } + + if (propertyPathStart.getSignaturePosition() >= parameters.length) { + throw new PropertyAccessException("Signature position out of range!"); + } + + return getPropertyContent(propertyPathStart.getPathToContinue(), parameters[propertyPathStart.getSignaturePosition()]); + case RETURN: + // we will not throw an exception here as the return value of a method can sometimes be + // null. If we throw an exception, this will lead to the removal of the path and thus no + // return value of this property accessor will be captured afterwards. + if (null == returnValue) { + return NULL_VALUE; + } else { + return getPropertyContent(propertyPathStart.getPathToContinue(), returnValue); + } + default: + throw new PropertyAccessException("Missing handler for type " + propertyPathStart.contentType); + } + } + + /** + * Checks whether or not the method may be called within the parameter storage algorithm. + * + * @param method + * The method name to check for. + * @return true if the method is accepted. + */ + private boolean isAcceptedMethod(String method) { + for (int i = 0; i < ALLOWED_METHODS.length; i++) { + String allowed = ALLOWED_METHODS[i]; + if (allowed.equals(method)) { + return true; + } + } + return false; + } + + /** + * Inner static recursive method to go along the given path. + * + * @see PropertyPath + * + * @param propertyPath + * The path to follow. + * @param object + * The object to analyze. + * @return The {@link String} representation of the field or parameter followed by the path. + * @throws PropertyAccessException + * This exception is thrown whenever something unexpectedly happens while accessing + * a property. + */ + private String getPropertyContent(PropertyPath propertyPath, Object object) throws PropertyAccessException { + if (null == object) { + return "null"; + } + + if (null == propertyPath) { + // end of the path to follow, return the String representation of + // the object + return object.toString(); + } + + Class c; + if (object instanceof Class) { + // This check is needed when a static class is passed to this + // method. + c = (Class) object; + } else { + c = object.getClass(); + } + + // We need to differ between calls of methods and the navigation of + // properties of an object. This differentiation is integrated to + // force the user to add () to the method to be called, thus the + // user is aware what he is doing and no unwanted method calls are + // performed. + if (propertyPath.isMethodCall()) { + + // strip the "()" from the path to find the method + String methodName = propertyPath.getName().substring(0, propertyPath.getName().length() - 2); + + // check if this method may be called + if (!isAcceptedMethod(methodName)) { + throw new PropertyAccessException("Method " + methodName + " MAY not be called!"); + } + + // special handling for the length method of Array objects + // Array objects do not inherit from the static Array class, thus + // trying to retrieve the method by reflection is not possible + if ("length".equals(methodName)) { + if (object.getClass().isArray()) { // ensure that we are really + // dealing with an array + return getPropertyContent(propertyPath.getPathToContinue(), Integer.valueOf(Array.getLength(object))); + } else { + log.error("Trying to access the lenght() method for a non array type"); + throw new PropertyAccessException("Trying to access the length() method for a non array type"); + } + } + + do { + // we are iterating using getDeclaredMethods as this call will + // also provide the default access and protected methods which + // the + // call to getMethods() will not + Method[] methods = c.getDeclaredMethods(); + for (int i = 0; i < methods.length; i++) { + Method method = methods[i]; + if (methodName.equals(method.getName())) { + + // We are only calling methods that do not take an + // argument + if (method.getParameterTypes().length != 0) { + if (log.isDebugEnabled()) { + log.debug("Skipping matching method " + method.getName() + " as it is not a no argument method"); + } + continue; + } + + try { + Object result = method.invoke(object, (Object[]) null); + return getPropertyContent(propertyPath.getPathToContinue(), result); + } catch (IllegalArgumentException e) { + log.error(e.getMessage()); + throw new PropertyAccessException("Illegal Argument Exception!", e); + } catch (IllegalAccessException e) { + log.error(e.getMessage()); + throw new PropertyAccessException("IllegalAccessException!", e); + } catch (InvocationTargetException e) { + log.error(e.getMessage()); + throw new PropertyAccessException("InvocationTargetException!", e); + } + + } + } + + c = c.getSuperclass(); + } while (c != Object.class); + + } else { // We are dealing with a property navigation and not an method + // call + do { + Field[] fields = c.getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + if (propertyPath.getName().equals(field.getName())) { + try { + field.setAccessible(true); + Object fieldObject = field.get(object); + return getPropertyContent(propertyPath.getPathToContinue(), fieldObject); + } catch (SecurityException e) { + log.error(e.getMessage()); + throw new PropertyAccessException("Security Exception was thrown while accessing a field!", e); + } catch (IllegalArgumentException e) { + log.error(e.getMessage()); + throw new PropertyAccessException("Illegal Argument Exception!", e); + } catch (IllegalAccessException e) { + log.error(e.getMessage()); + throw new PropertyAccessException("Illegal Access Exception!", e); + } + } + } + + c = c.getSuperclass(); + } while (c != Object.class); + } + + throw new PropertyAccessException("Property or method " + propertyPath.getName() + " cannot be found in class " + object.getClass() + "!"); + } + + /** + * {@inheritDoc} + */ + public List getParameterContentData(List propertyAccessorList, Object clazz, Object[] parameters, Object returnValue) { + List parameterContentData = new ArrayList(); + for (Iterator iterator = propertyAccessorList.iterator(); iterator.hasNext();) { + PropertyPathStart start = iterator.next(); + + try { + String content = this.getPropertyContent(start, clazz, parameters, returnValue); + ParameterContentData paramContentData = new ParameterContentData(); + paramContentData.setContent(content); + paramContentData.setContentType(start.getContentType()); + paramContentData.setName(start.getName()); + paramContentData.setSignaturePosition(start.getSignaturePosition()); + parameterContentData.add(paramContentData); + } catch (PropertyAccessException e) { + if (log.isErrorEnabled()) { + log.error("Cannot access the property: " + start + " for class " + clazz + ". Will be removed from the list to prevent further errors! (" + e.getMessage() + ")"); + } + + propertyAccessorList.remove(start); + // iterator.remove(); // Unsupported exception. Iterator can't make changes, since + // iterating over a snapshot. + + } + + } + + return parameterContentData; + } + + /** + * Every path can have another follower path. These classes are used to describe the way to find + * a specific property in an object. + * + * @author Patrice Bouillet + * + */ + public static class PropertyPath { + + /** + * The name of this path. + */ + private String name; + + /** + * The path to continue. + */ + private PropertyPath pathToContinue; + + /** + * Creates a new instance and leaves the name empty. + */ + public PropertyPath() { + } + + /** + * Creates a new instance and sets the name. + * + * @param name + * the name of this path. + */ + public PropertyPath(String name) { + this.name = name; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPathToContinue(PropertyPath pathToContinue) { + this.pathToContinue = pathToContinue; + } + + public PropertyPath getPathToContinue() { + return pathToContinue; + } + + public boolean isMethodCall() { + return name.endsWith("()"); + } + + /** + * {@inheritDoc} + */ + public String toString() { + if (null != pathToContinue) { + return name + "-->" + pathToContinue.toString(); + } else { + return name; + } + } + + } + + /** + * The start definition of a property accessor. + * + * @author Patrice Bouillet + * + */ + public static class PropertyPathStart extends PropertyPath { + + /** + * Defines what type of property we are capturing. + */ + private ParameterContentType contentType; + + /** + * The position of the parameter in the signature if the classOfExecutedMethod + * value is set to false. Only set if contentType is set to + * Parameter + */ + private int signaturePosition = -1; + + /** + * Gets {@link #contentType}. + * + * @return {@link #contentType} + */ + public ParameterContentType getContentType() { + return contentType; + } + + /** + * Sets {@link #contentType}. + * + * @param contentType + * New value for {@link #platformIdent} + */ + public void setContentType(ParameterContentType contentType) { + this.contentType = contentType; + } + + /** + * sets the position of the parameter in the signature to read. + * + * @param signaturePosition + * the position of the parameter in the signature to read. + */ + public void setSignaturePosition(int signaturePosition) { + this.signaturePosition = signaturePosition; + } + + /** + * returns the position of the parameter in the signature to read. + * + * @return position of the parameter in the signature to read. + */ + public int getSignaturePosition() { + return signaturePosition; + } + + /** + * {@inheritDoc} + */ + public String toString() { + if (null != getPathToContinue()) { + return "[" + getName() + "] " + getPathToContinue().toString(); + } else { + return "[" + getName() + "]"; + } + } + + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/config/impl/RegisteredSensorConfig.java b/Agent/src/info/novatec/inspectit/agent/config/impl/RegisteredSensorConfig.java new file mode 100644 index 000000000..1a4827953 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/impl/RegisteredSensorConfig.java @@ -0,0 +1,339 @@ +package info.novatec.inspectit.agent.config.impl; + +import info.novatec.inspectit.agent.hooking.IHook; +import info.novatec.inspectit.agent.hooking.IMethodHook; +import info.novatec.inspectit.agent.sensor.method.IMethodSensor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import javassist.CtBehavior; +import javassist.Modifier; + +/** + * After a sensor is registered at the CMR, this class is used to store all the information as the + * {@link UnregisteredSensorConfig} contains information which is not needed anymore. Every + * {@link RegisteredSensorConfig} class maps directly to one specific class and method with its + * parameters. + * + * @author Patrice Bouillet + * @author Eduard Tudenhoefner + * + */ +public class RegisteredSensorConfig extends AbstractSensorConfig { + + /** + * The {@link CtBehavior} object corresponding to this sensor configuration. + */ + private CtBehavior ctBehavior; + + /** + * The hash value. + */ + private long id; + + /** + * The return type of the method. + */ + private String returnType = ""; + + /** + * This list contains all configurations of the sensor types for this sensor configuration. + */ + private List sensorTypeConfigs = new ArrayList(); + + /** + * The sensor type configuration object of the invocation sequence tracer. + */ + private MethodSensorTypeConfig invocationSequenceSensorTypeConfig = null; + + /** + * The sensor type configuration object of the exception sensor. + */ + private MethodSensorTypeConfig exceptionSensorTypeConfig = null; + + /** + * The map used by the hooks in the source code to execute the after methods. + */ + private Map methodHookMap = new LinkedHashMap(); + + /** + * The map used by the hooks in the source code to execute the before method. For that hook, it + * has to be reversed. + */ + private Map reverseMethodHookMap = new LinkedHashMap(); + + /** + * The method visibility. + */ + private int modifiers; + + /** + * Sets the {@link CtBehavior} object. + * + * @param behavior + * The {@link CtBehavior} object. + */ + public void setCtBehavior(CtBehavior behavior) { + this.ctBehavior = behavior; + } + + /** + * Returns the {@link CtBehavior} object of this sensor configuration. + * + * @return The {@link CtBehavior} object of this sensor configuration. + */ + public CtBehavior getCtBehavior() { + return ctBehavior; + } + + /** + * The unique id. + * + * @return The unique id. + */ + public long getId() { + return id; + } + + /** + * Set the unique id. + * + * @param id + * The unique id. + */ + public void setId(long id) { + this.id = id; + } + + /** + * Returns the return type of the method. + * + * @return The method return type. + */ + public String getReturnType() { + return returnType; + } + + /** + * Sets the return type of the method. + * + * @param returnType + * The return type to set. + */ + public void setReturnType(String returnType) { + this.returnType = returnType; + } + + /** + * Returns the list containing all the sensor type configurations. + * + * @return The list of sensor type configurations. + */ + public List getSensorTypeConfigs() { + return sensorTypeConfigs; + } + + /** + * Adds a sensor type to this configuration. + * + * @param sensorTypeConfig + * The sensor type to add. + */ + public void addSensorTypeConfig(MethodSensorTypeConfig sensorTypeConfig) { + // check for the invocation sequence sensor type + if (sensorTypeConfig.getClassName().endsWith("InvocationSequenceSensor")) { + invocationSequenceSensorTypeConfig = sensorTypeConfig; + } + + if (sensorTypeConfig.getClassName().endsWith("ExceptionSensor")) { + exceptionSensorTypeConfig = sensorTypeConfig; + } + + if (!sensorTypeConfigs.contains(sensorTypeConfig)) { + sensorTypeConfigs.add(sensorTypeConfig); + + sortSensorTypeConfigs(); + sortMethodHooks(); + + // TODO + // getCoreService().getIdManager().addSensorTypeToMethod( + // sensorTypeConfig.getId(), getId()); + } + } + + /** + * Remove a sensor type from this configuration. + * + * @param sensorTypeConfig + * The sensor type to remove. + */ + public void removeSensorTypeConfig(MethodSensorTypeConfig sensorTypeConfig) { + if (sensorTypeConfigs.contains(sensorTypeConfig)) { + sensorTypeConfigs.remove(sensorTypeConfig); + + sortMethodHooks(); + + // TODO remove at the server + } + } + + /** + * The sensor comparator is used to sort the sensor type configurations according to their + * priority. + */ + private Comparator sensorTypeConfigComparator = new SensorTypeConfigComparator(); + + /** + * Sort the sensor type configurations according to their priority. + */ + private void sortSensorTypeConfigs() { + if (sensorTypeConfigs.size() > 1) { + Collections.sort(sensorTypeConfigs, sensorTypeConfigComparator); + } + } + + /** + * Returns the sorted method hooks as a {@link Map} with the hash value of the corresponding + * sensor type as the key and the {@link IMethodHook} implementation as the value. + *

+ * The returned {@link Map} of this method is not locked for modification because of performance + * reasons. + *

+ * This map is always generated along calling + * {@link #addSensorTypeConfig(MethodSensorTypeConfig)} and + * {@link #removeSensorTypeConfig(MethodSensorTypeConfig)}. + * + * @return The sorted map of method hooks. + */ + public Map getMethodHooks() { + return methodHookMap; + } + + /** + * Returns the same map as returned by {@link #getMethodHooks()} but in reverse order. + * + * @return The reverse sorted map of method hooks. + */ + public Map getReverseMethodHooks() { + return reverseMethodHookMap; + } + + /** + * Initialized the method hooks map by iterating over the list containing the sensorTypeConfigs. + */ + private void sortMethodHooks() { + methodHookMap.clear(); + for (MethodSensorTypeConfig sensorTypeConfig : sensorTypeConfigs) { + IMethodSensor methodSensor = (IMethodSensor) sensorTypeConfig.getSensorType(); + methodHookMap.put(Long.valueOf(sensorTypeConfig.getId()), methodSensor.getHook()); + } + + reverseMethodHookMap.clear(); + for (ListIterator iterator = sensorTypeConfigs.listIterator(sensorTypeConfigs.size()); iterator.hasPrevious();) { + MethodSensorTypeConfig sensorTypeConfig = iterator.previous(); + IMethodSensor methodSensor = (IMethodSensor) sensorTypeConfig.getSensorType(); + reverseMethodHookMap.put(Long.valueOf(sensorTypeConfig.getId()), methodSensor.getHook()); + } + } + + /** + * Inner class to sort the sensor list according to their priority. + * + * @author Patrice Bouillet + * + */ + private static class SensorTypeConfigComparator implements Comparator, Serializable { + + /** + * The generated serial version UID. + */ + private static final long serialVersionUID = -2156911328015024777L; + + /** + * {@inheritDoc} + */ + public int compare(MethodSensorTypeConfig sensorTypeConfig1, MethodSensorTypeConfig sensorTypeConfig2) { + return sensorTypeConfig2.getPriority().compareTo(sensorTypeConfig1.getPriority()); + } + + } + + /** + * Checks if this sensor configuration starts an invocation sequence tracer. This has to be + * checked separately. + * + * @return If this config starts an invocation sequence tracer. + */ + public boolean startsInvocationSequence() { + return null != invocationSequenceSensorTypeConfig; + } + + /** + * Returns the exception sensor type configuration object. Can be null. + * + * @return The exception sensor type configuration. + */ + public MethodSensorTypeConfig getExceptionSensorTypeConfig() { + return exceptionSensorTypeConfig; + } + + /** + * Sets the exception sensor type configuration. + * + * @param exceptionSensorTypeConfig + * The exception sensor type configuration. + */ + public void setExceptionSensorTypeConfig(MethodSensorTypeConfig exceptionSensorTypeConfig) { + this.exceptionSensorTypeConfig = exceptionSensorTypeConfig; + + // we need to add the sensor type config separately to the list, because + // calling sortMethodHooks causes a ClassCastException + if (!sensorTypeConfigs.contains(this.exceptionSensorTypeConfig)) { + sensorTypeConfigs.add(this.exceptionSensorTypeConfig); + sortSensorTypeConfigs(); + } + } + + /** + * Returns the invocation sequence sensor type configuration object. Can be null. + * + * @return The invocation sequence sensor type configuration. + */ + public MethodSensorTypeConfig getInvocationSequenceSensorTypeConfig() { + return invocationSequenceSensorTypeConfig; + } + + /** + * Sets the modifiers. + * + * @param modifiers + * The int value of the modifiers. + */ + public void setModifiers(int modifiers) { + this.modifiers = modifiers; + } + + /** + * Returns the modifiers. + * + * @return The modifiers. + */ + public int getModifiers() { + return modifiers; + } + + /** + * {@inheritDoc} + */ + public String toString() { + return id + " :: " + Modifier.toString(modifiers) + " " + getTargetPackageName() + "." + getTargetClassName() + "#" + getTargetMethodName() + "(" + getParameterTypes() + ")"; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/config/impl/RepositoryConfig.java b/Agent/src/info/novatec/inspectit/agent/config/impl/RepositoryConfig.java new file mode 100644 index 000000000..45461c4cf --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/impl/RepositoryConfig.java @@ -0,0 +1,53 @@ +package info.novatec.inspectit.agent.config.impl; + +/** + * Class used by the {@link ConfigurationStorage} implementation to store the information of a + * repository. + * + * @author Patrice Bouillet + * + */ +public class RepositoryConfig { + + /** + * The host name / ip. + */ + private String host; + + /** + * The port of the host. + */ + private int port; + + /** + * Default constructor accepting two parameters. + * + * @param host + * The host name / ip. + * @param port + * The port of the host. + */ + public RepositoryConfig(String host, int port) { + this.host = host; + this.port = port; + } + + /** + * Returns the name / ip of the host. + * + * @return The name / ip of the host. + */ + public String getHost() { + return host; + } + + /** + * Returns the port of the host. + * + * @return The port of the host. + */ + public int getPort() { + return port; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/config/impl/StrategyConfig.java b/Agent/src/info/novatec/inspectit/agent/config/impl/StrategyConfig.java new file mode 100644 index 000000000..c790e5042 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/impl/StrategyConfig.java @@ -0,0 +1,55 @@ +package info.novatec.inspectit.agent.config.impl; + +import java.util.Map; + +/** + * Class used by the {@link ConfigurationStorage} to store the information of sending strategies or + * the buffer strategy. + * + * @author Patrice Bouillet + * + */ +public class StrategyConfig { + + /** + * The fully qualified class name. + */ + private String clazzName; + + /** + * Additional settings stored in a map. + */ + private Map settings; + + /** + * Default constructor accepting 2 parameters. + * + * @param clazzName + * The fully qualified class name. + * @param settings + * Additional settings stored in a map. + */ + public StrategyConfig(String clazzName, Map settings) { + this.clazzName = clazzName; + this.settings = settings; + } + + /** + * Returns the fully qualified class name of this configuration. + * + * @return The fully qualified class name of this configuration. + */ + public String getClazzName() { + return clazzName; + } + + /** + * Returns the settings of this configuration. + * + * @return The settings of this configuration. + */ + public Map getSettings() { + return settings; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/config/impl/UnregisteredSensorConfig.java b/Agent/src/info/novatec/inspectit/agent/config/impl/UnregisteredSensorConfig.java new file mode 100644 index 000000000..9a6321283 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/config/impl/UnregisteredSensorConfig.java @@ -0,0 +1,337 @@ +package info.novatec.inspectit.agent.config.impl; + +import info.novatec.inspectit.agent.analyzer.IClassPoolAnalyzer; +import info.novatec.inspectit.agent.analyzer.IInheritanceAnalyzer; +import info.novatec.inspectit.agent.analyzer.IMatcher; +import info.novatec.inspectit.agent.analyzer.impl.AnnotationMatcher; +import info.novatec.inspectit.agent.analyzer.impl.DirectMatcher; +import info.novatec.inspectit.agent.analyzer.impl.IndirectMatcher; +import info.novatec.inspectit.agent.analyzer.impl.InterfaceMatcher; +import info.novatec.inspectit.agent.analyzer.impl.ModifierMatcher; +import info.novatec.inspectit.agent.analyzer.impl.SimpleMatchPattern; +import info.novatec.inspectit.agent.analyzer.impl.SuperclassMatcher; +import info.novatec.inspectit.agent.analyzer.impl.ThrowableMatcher; + +import java.util.List; +import java.util.regex.Matcher; + +import javassist.Modifier; + +/** + * Container for the values of a sensor configuration. It stores all the values defined in a config + * file for later access. + * + * @author Patrice Bouillet + */ +public class UnregisteredSensorConfig extends AbstractSensorConfig { + + /** + * The class pool analyzer. + */ + private final IClassPoolAnalyzer classPoolAnalyzer; + + /** + * The inheritance analyzer. + */ + private final IInheritanceAnalyzer inheritanceAnalyzer; + + /** + * If this config defines a superclass. + */ + private boolean superclass = false; + + /** + * If this config defines an interface. + */ + private boolean interf = false; + + /** + * If this config is virtual, so patterns are used here. + */ + private boolean virtual = false; + + /** + * Defines if all methods with the given sensorName are instrumented regardless of the + * signatures. + */ + private boolean ignoreSignature = false; + + /** + * Determines whether the exception sensor is activated. + */ + private boolean exceptionSensorActivated = false; + + /** + * Integer value defining the modifier. Values are defined in {@link Modifier} class. Default + * value is 0, which means that no modifiers were set. + */ + private int modifiers = 0; + + /** + * The matcher used to compare class name / method name and all method parameters. + */ + private IMatcher matcher; + + /** + * The sensor type of this configuration. As there can be only one, this is just a direct + * reference. + */ + private MethodSensorTypeConfig sensorTypeConfig; + + /** + * Annotation that is defining what has to be instrumented. + */ + private String annotationClassName; + + /** + * Default constructor which accepts 2 parameter. + * + * @param classPoolAnalyzer + * The class pool analyzer. + * @param inheritanceAnalyzer + * The inheritance analyzer. + */ + public UnregisteredSensorConfig(IClassPoolAnalyzer classPoolAnalyzer, IInheritanceAnalyzer inheritanceAnalyzer) { + this.classPoolAnalyzer = classPoolAnalyzer; + this.inheritanceAnalyzer = inheritanceAnalyzer; + } + + /** + * {@inheritDoc} + */ + public void setTargetClassName(String targetClassName) { + super.setTargetClassName(targetClassName); + + if (SimpleMatchPattern.isPattern(targetClassName)) { + setVirtual(true); + } + } + + /** + * {@inheritDoc} + */ + public void setTargetMethodName(String targetMethodName) { + super.setTargetMethodName(targetMethodName); + + if (SimpleMatchPattern.isPattern(targetMethodName)) { + setVirtual(true); + } + } + + /** + * {@inheritDoc} + */ + public void setParameterTypes(List parameterTypes) { + if (null != parameterTypes) { + super.setParameterTypes(parameterTypes); + + for (String parameter : parameterTypes) { + if (SimpleMatchPattern.isPattern(parameter)) { + setVirtual(true); + } + } + } + } + + /** + * Returns if this sensor configuration is defining a superclass. + * + * @return Returns if this is a config for a superclass. + */ + public boolean isSuperclass() { + return superclass; + } + + /** + * Sets if this configuration defines a superclass. Defaults to false. + * + * @param superclass + * Setting if this configuration defines a superclass. + */ + public void setSuperclass(boolean superclass) { + this.superclass = superclass; + } + + /** + * Returns if this sensor configuration is defining a superclass. + * + * @return Returns if this is a config for a superclass. + */ + public boolean isInterface() { + return interf; + } + + /** + * Sets if this configuration defines an interface. Defaults to false. + * + * @param interf + * Setting if this configuration defines an interface. + */ + public void setInterface(boolean interf) { + this.interf = interf; + } + + /** + * Is this configuration just virtual, as the method name or the names of the parameters contain + * symbols for pattern matching. + * + * @return Returns if this configuration is virtual. + */ + public boolean isVirtual() { + return virtual; + } + + /** + * Setting the virtual state. Defaults to false. + * + * @param virtual + * The parameter to define the virtual state. + */ + public void setVirtual(boolean virtual) { + this.virtual = virtual; + } + + /** + * Returns if this sensor config ignores the signatures of the methods that fit to the name. + * + * @return If this config ignores the method signature. + */ + public boolean isIgnoreSignature() { + return ignoreSignature; + } + + /** + * If this sensor config ignores the signature. + * + * @param ignoreSignature + * The boolean value, default is false. + */ + public void setIgnoreSignature(boolean ignoreSignature) { + this.ignoreSignature = ignoreSignature; + } + + /** + * Returns the sensor type configuration. + * + * @return The sensor type configuration. + */ + public MethodSensorTypeConfig getSensorTypeConfig() { + return sensorTypeConfig; + } + + /** + * Sets the sensor type configuration. + * + * @param sensorTypeConfig + * The sensor type configuration. + */ + public void setSensorTypeConfig(MethodSensorTypeConfig sensorTypeConfig) { + this.sensorTypeConfig = sensorTypeConfig; + } + + /** + * Defines whether exception sensor is activated. + * + * @param exceptionSensorActivated + * The flag indicating whether exception sensor is activated or not. + */ + public void setExceptionSensorActivated(boolean exceptionSensorActivated) { + this.exceptionSensorActivated = exceptionSensorActivated; + } + + /** + * Gets {@link #exceptionSensorActivated}. + * + * @return {@link #exceptionSensorActivated} + */ + public boolean isExceptionSensorActivated() { + return exceptionSensorActivated; + } + + /** + * Returns the integer value that defines the modifiers of methods to be instrumented. The + * values are defined in {@link Modifier} class. Default value is 0, and this means no modifiers + * are set. + * + * @return the modifiers int value + */ + public int getModifiers() { + return modifiers; + } + + /** + * Sets the integer value that defines the modifiers of methods to be instrumented. The values + * are defined in {@link Modifier} class. Default value is 0, and this means no modifiers are + * set. + * + * @param modifiers + * the modifier int value + */ + public void setModifiers(int modifiers) { + this.modifiers = modifiers; + } + + /** + * Returns the matcher which is used by this sensor configuration. + * + * @return The {@link Matcher}. + */ + public IMatcher getMatcher() { + return matcher; + } + + /** + * Annotation class name that is defining what needs to be instrumented. + * + * @return the annotation class name + */ + public String getAnnotationClassName() { + return annotationClassName; + } + + /** + * Sets the annotation class name. If the annotation is set, the classes and method will be + * matched based on this annotation. + * + * @param annotationClassName + * the annotation to set + */ + public void setAnnotationClassName(String annotationClassName) { + this.annotationClassName = annotationClassName; + } + + /** + * Completes the whole configuration. Has to be called after all settings are set. + */ + public void completeConfiguration() { + if (!virtual && !superclass && !interf) { + matcher = new DirectMatcher(classPoolAnalyzer, this); + } else if (superclass && !interf) { + matcher = new SuperclassMatcher(inheritanceAnalyzer, classPoolAnalyzer, this); + } else if (!superclass && interf) { + matcher = new InterfaceMatcher(inheritanceAnalyzer, classPoolAnalyzer, this); + } else if (virtual && !superclass && !interf) { + matcher = new IndirectMatcher(classPoolAnalyzer, this); + } + + if (null != annotationClassName) { + matcher = new AnnotationMatcher(inheritanceAnalyzer, classPoolAnalyzer, this, matcher); + } + + if (exceptionSensorActivated) { + matcher = new ThrowableMatcher(inheritanceAnalyzer, classPoolAnalyzer, this, matcher); + } + + if (modifiers != 0) { + matcher = new ModifierMatcher(classPoolAnalyzer, this, matcher); + } + } + + /** + * {@inheritDoc} + */ + public String toString() { + return super.toString() + " superclass:" + superclass + " interface:" + interf + " virtual:" + virtual; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/connection/AbstractRemoteMethodCall.java b/Agent/src/info/novatec/inspectit/agent/connection/AbstractRemoteMethodCall.java new file mode 100644 index 000000000..89d4644ee --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/connection/AbstractRemoteMethodCall.java @@ -0,0 +1,106 @@ +package info.novatec.inspectit.agent.connection; + +import info.novatec.inspectit.agent.connection.impl.AdditiveWaitRetryStrategy; + +import java.rmi.Remote; +import java.rmi.RemoteException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * IMPORTANT: The class code is copied/taken from O'REILLY onJava.com. Original + * author is William Grosso. License info can be found here. + * + * @author William Grosso + */ +public abstract class AbstractRemoteMethodCall { + + /** + * The logger of this class. Initialized manually. + */ + private static final Logger LOG = LoggerFactory.getLogger(AbstractRemoteMethodCall.class); + + /** + * Performs the actual call to the server. + * + * @return The object returned from the server (if there is one). + * @throws ServerUnavailableException + * Throws a ServerUnavailable exception if the server isn't available anymore due to + * network problems or something else. + */ + public final Object makeCall() throws ServerUnavailableException { + RetryStrategy strategy = getRetryStrategy(); + while (strategy.shouldRetry()) { + Remote remoteObject = getRemoteObject(); + if (null == remoteObject) { + throw new ServerUnavailableException(); + } + try { + return performRemoteCall(remoteObject); + } catch (RemoteException remoteException) { + try { + strategy.remoteExceptionOccured(); + } catch (RetryException retryException) { + handleRetryException(remoteObject); + } + } + } + return null; + } + + /* + * The next 4 methods define the core behavior. Of these, two must be implemented by the + * subclass (and so are left abstract). The remaining three can be altered to provide customized + * retry handling. + */ + + /** + * getRemoteObject is a template method which should, in most cases, return the stub. + * + * @return The Remote Stub + * @throws ServerUnavailableException + * Throws a ServerUnavailable exception if the server isn't available anymore due to + * network problems or something else. + */ + protected abstract Remote getRemoteObject() throws ServerUnavailableException; + + /** + * performRemoteCall is a template method which actually makes the remote method invocation. + * + * @param remoteObject + * The actual remote object. + * @return The {@link Object} received from the server. + * @throws RemoteException + * If an exception was thrown during the call on the server. + */ + protected abstract Object performRemoteCall(Remote remoteObject) throws RemoteException; + + /** + * Returns the selected retry strategy. + * + * @return The retry strategy. + */ + protected final RetryStrategy getRetryStrategy() { + return new AdditiveWaitRetryStrategy(); + } + + /** + * This method is executed if some calls to the server weren't successful. + * + * @param remoteObject + * The remote object. + * @throws ServerUnavailableException + * The exception {@link ServerUnavailableException} is always thrown when this + * method is entered. + */ + protected final void handleRetryException(final Remote remoteObject) throws ServerUnavailableException { + if (LOG.isDebugEnabled()) { + LOG.debug("Repeated attempts to communicate with " + remoteObject + " failed."); + } + throw new ServerUnavailableException(); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/connection/IConnection.java b/Agent/src/info/novatec/inspectit/agent/connection/IConnection.java new file mode 100644 index 000000000..bc06f4da1 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/connection/IConnection.java @@ -0,0 +1,149 @@ +package info.novatec.inspectit.agent.connection; + +import info.novatec.inspectit.agent.config.impl.MethodSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.PlatformSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.communication.DefaultData; + +import java.net.ConnectException; +import java.util.List; + +/** + * The connection interface to implement different connection types, like RMI, Corba, etc. + * + * @author Patrice Bouillet + * + */ +public interface IConnection { + + /** + * Establish the connection to the server. + * + * @param host + * The host / ip of the server. + * @param port + * The port of the server. + * @exception ConnectException + * Throws a ConnectException if there was a problem connecting to the repository. + */ + void connect(String host, int port) throws ConnectException; + + /** + * Disconnect from the server if possible. + */ + void disconnect(); + + /** + * Returns if the connection is initialized and ready. + * + * @return Is the connection initialized and ready to use. + */ + boolean isConnected(); + + /** + * Send the measurements to the server for further processing. + * + * @param dataObjects + * The measurements to send. + * @throws ServerUnavailableException + * If the sending wasn't successful in any way, a {@link ServerUnavailableException} + * exception is thrown. + */ + void sendDataObjects(List dataObjects) throws ServerUnavailableException; + + /** + * Registers the current platform (composed of the network interface with the Agent name) in the + * CMR and returns a unique value for this platform. + * + * @param agentName + * The name of the agent. + * @param version + * The version of the agent. + * @return The unique id for this platform. + * @throws ServerUnavailableException + * If the sending wasn't successful in any way, a {@link ServerUnavailableException} + * exception is thrown. + * @throws RegistrationException + * This exception is thrown when a problem with the registration process appears. + */ + long registerPlatform(String agentName, String version) throws ServerUnavailableException, RegistrationException; + + /** + * Unregisters the platform in the CMR by sending the agent name and the network interfaces + * defined by the machine. + * + * @param agentName + * Name of the Agent. + * @throws RegistrationException + * This exception is thrown when a problem with the un-registration process appears. + */ + void unregisterPlatform(String agentName) throws RegistrationException; + + /** + * Registers the specified parameters at the server and returns a unique identifier which will + * be used throughout the sensors. + * + * @param platformId + * The unique id for this platform. + * @param sensorConfig + * The registered sensor configuration. + * + * @return Returns the unique identifier. + * @throws ServerUnavailableException + * If the sending wasn't successful in any way, a {@link ServerUnavailableException} + * exception is thrown. + * @throws RegistrationException + * This exception is thrown when a problem with the registration process appears. + */ + long registerMethod(long platformId, RegisteredSensorConfig sensorConfig) throws ServerUnavailableException, RegistrationException; + + /** + * Registers the specified method sensor type at the CMR. + * + * @param platformId + * The unique id for this platform. + * @param methodSensorTypeConfig + * The unregistered sensor type configuration. + * + * @return Returns the unique identifier. + * @throws ServerUnavailableException + * If the sending wasn't successful in any way, a {@link ServerUnavailableException} + * exception is thrown. + * @throws RegistrationException + * This exception is thrown when a problem with the registration process appears. + */ + long registerMethodSensorType(long platformId, MethodSensorTypeConfig methodSensorTypeConfig) throws ServerUnavailableException, RegistrationException; + + /** + * Registers the specified platform sensor type at the CMR. + * + * @param platformId + * The unique id for this platform. + * @param platformSensorTypeConfig + * The unregistered sensor type configuration. + * + * @return Returns the unique identifier. + * @throws ServerUnavailableException + * If the sending wasn't successful in any way, a {@link ServerUnavailableException} + * exception is thrown. + * @throws RegistrationException + * This exception is thrown when a problem with the registration process appears. + */ + long registerPlatformSensorType(long platformId, PlatformSensorTypeConfig platformSensorTypeConfig) throws ServerUnavailableException, RegistrationException; + + /** + * Adds a sensor type to an already registered sensor at the CMR. + * + * @param sensorTypeId + * The id of the sensor type. + * @param methodId + * The id of the method. + * @throws ServerUnavailableException + * If the sending wasn't successful in any way, a {@link ServerUnavailableException} + * exception is thrown. + * @throws RegistrationException + * This exception is thrown when a problem with the registration process appears. + */ + void addSensorTypeToMethod(long sensorTypeId, long methodId) throws ServerUnavailableException, RegistrationException; + +} diff --git a/Agent/src/info/novatec/inspectit/agent/connection/RegistrationException.java b/Agent/src/info/novatec/inspectit/agent/connection/RegistrationException.java new file mode 100644 index 000000000..e76df32c6 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/connection/RegistrationException.java @@ -0,0 +1,46 @@ +package info.novatec.inspectit.agent.connection; + +/** + * The registration exception which is thrown whenever a problem occurs in the registration process. + * + * @author Patrice Bouillet + * + */ +public class RegistrationException extends Exception { + + /** + * The serial version UID. + */ + private static final long serialVersionUID = 1462176297215480008L; + + /** + * Constructs a new exception with the specified detail message. The cause is not initialized, + * and may subsequently be initialized by a call to {@link #initCause}. + * + * @param message + * The detail message. The detail message is saved for later retrieval by the + * {@link #getMessage()} method. + */ + public RegistrationException(String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and cause. + *

+ * Note that the detail message associated with cause is not automatically + * incorporated in this exception's detail message. + * + * @param message + * the detail message (which is saved for later retrieval by the + * {@link #getMessage()} method). + * @param cause + * The cause (which is saved for later retrieval by the {@link #getCause()} method). + * (A null value is permitted, and indicates that the cause is nonexistent + * or unknown.) + */ + public RegistrationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/connection/RetryException.java b/Agent/src/info/novatec/inspectit/agent/connection/RetryException.java new file mode 100644 index 000000000..457c42d67 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/connection/RetryException.java @@ -0,0 +1,36 @@ +package info.novatec.inspectit.agent.connection; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +/** + * IMPORTANT: The class code is copied/taken from O'REILLY onJava.com. Original + * author is William Grosso. License info can be found here. + * + * @author William Grosso + */ + +public class RetryException extends Exception implements Externalizable { + + /** + * The serial version UID of this class. + */ + private static final long serialVersionUID = 0L; + + /** + * {@inheritDoc} + */ + public void readExternal(ObjectInput input) throws IOException, ClassNotFoundException { + } + + /** + * {@inheritDoc} + */ + public void writeExternal(ObjectOutput output) throws IOException { + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/connection/RetryStrategy.java b/Agent/src/info/novatec/inspectit/agent/connection/RetryStrategy.java new file mode 100644 index 000000000..61c130b31 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/connection/RetryStrategy.java @@ -0,0 +1,87 @@ +package info.novatec.inspectit.agent.connection; + +/** + * IMPORTANT: The class code is copied/taken from O'REILLY onJava.com. Original + * author is William Grosso. License info can be found here. + * + * @author William Grosso + */ +public abstract class RetryStrategy { + + /** + * The default number of retries. + */ + public static final int DEFAULT_NUMBER_OF_RETRIES = 3; + + /** + * How many tries are left till we go on. + */ + private int numberOfTriesLeft; + + /** + * Initializes the class with the default number of retries. + */ + public RetryStrategy() { + this(DEFAULT_NUMBER_OF_RETRIES); + } + + /** + * Initializes the class with the given number of retries. + * + * @param numberOfRetries + * The number of retries to use. + */ + public RetryStrategy(final int numberOfRetries) { + numberOfTriesLeft = numberOfRetries; + } + + /** + * Shall we retry when an error occurs? + * + * @return If we will retry the sending. + */ + public final boolean shouldRetry() { + return 0 < numberOfTriesLeft; + } + + /** + * Called when a remote exception occured at the server. Two options are available here, the + * first is to raise an exception, and the second is to wait till we are going for a retry. + * + * @throws RetryException + * Thrown if we won't try to send data anymore. + */ + public final void remoteExceptionOccured() throws RetryException { + numberOfTriesLeft--; + + if (!shouldRetry()) { + throw new RetryException(); + } + + waitUntilNextTry(); + } + + /** + * Has to be overwritten by subclasses to specify the time till we'll try the next + * connecting/sending. + * + * @return Returns a value in milliseconds of how long we'll wait. + */ + protected abstract long getTimeToWait(); + + /** + * Will suspend the actual thread and waits for the time we get through {@link #getTimeToWait()} + * . + */ + private void waitUntilNextTry() { + long timeToWait = getTimeToWait(); + + try { + Thread.sleep(timeToWait); + } catch (InterruptedException ignored) { // NOCHK + // nothing to do here + } + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/connection/ServerUnavailableException.java b/Agent/src/info/novatec/inspectit/agent/connection/ServerUnavailableException.java new file mode 100644 index 000000000..10116df52 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/connection/ServerUnavailableException.java @@ -0,0 +1,35 @@ +package info.novatec.inspectit.agent.connection; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +/** + * IMPORTANT: The class code is copied/taken from O'REILLY onJava.com. Original + * author is William Grosso. License info can be found here. + * + * @author William Grosso + */ +public class ServerUnavailableException extends Exception implements Externalizable { + + /** + * The serial version UID of this class. + */ + private static final long serialVersionUID = 0L; + + /** + * {@inheritDoc} + */ + public void readExternal(ObjectInput input) throws IOException, ClassNotFoundException { + } + + /** + * {@inheritDoc} + */ + public void writeExternal(ObjectOutput output) throws IOException { + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/connection/impl/AddDataObjects.java b/Agent/src/info/novatec/inspectit/agent/connection/impl/AddDataObjects.java new file mode 100644 index 000000000..7ebdbc465 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/connection/impl/AddDataObjects.java @@ -0,0 +1,61 @@ +package info.novatec.inspectit.agent.connection.impl; + +import info.novatec.inspectit.agent.connection.AbstractRemoteMethodCall; +import info.novatec.inspectit.agent.connection.ServerUnavailableException; +import info.novatec.inspectit.cmr.service.IAgentStorageService; +import info.novatec.inspectit.communication.DefaultData; + +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.util.List; + +/** + * Class which encapsulates the request to the {@link Remote} object {@link IRepository}. + * + * @author Patrice Bouillet + * + */ +public class AddDataObjects extends AbstractRemoteMethodCall { + + /** + * The reference to the repository which accepts our data. + */ + private final Remote repository; + + /** + * A list containing our measurements we want to send. + */ + private final List dataObjects; + + /** + * The only constructor for this class accepts 2 attributes. The first one is the {@link Remote} + * object, which will be used to send the data. The second one, a {@link List} of measurements, + * is the actual data. + * + * @param repository + * The {@link Remote} object. + * @param dataObjects + * The {@link List} of data objects to send. + */ + public AddDataObjects(IAgentStorageService repository, List dataObjects) { + this.repository = repository; + this.dataObjects = dataObjects; + } + + /** + * {@inheritDoc} + */ + protected Remote getRemoteObject() throws ServerUnavailableException { + return repository; + } + + /** + * {@inheritDoc} + */ + protected Object performRemoteCall(Remote remoteObject) throws RemoteException { + IAgentStorageService repo = (IAgentStorageService) remoteObject; + repo.addDataObjects(dataObjects); + return null; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/connection/impl/AddSensorTypeToMethod.java b/Agent/src/info/novatec/inspectit/agent/connection/impl/AddSensorTypeToMethod.java new file mode 100644 index 000000000..7bf997c22 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/connection/impl/AddSensorTypeToMethod.java @@ -0,0 +1,65 @@ +package info.novatec.inspectit.agent.connection.impl; + +import info.novatec.inspectit.agent.connection.AbstractRemoteMethodCall; +import info.novatec.inspectit.agent.connection.ServerUnavailableException; +import info.novatec.inspectit.cmr.service.IRegistrationService; + +import java.rmi.Remote; +import java.rmi.RemoteException; + +/** + * Class which encapsulates the request to the {@link Remote} object + * {@link IRegistration#addTypeToSensor(String, String, String, int)}. + * + * @author Patrice Bouillet + */ +public class AddSensorTypeToMethod extends AbstractRemoteMethodCall { + + /** + * The registration object which is used for the actual registering. + */ + private final Remote registrationService; + + /** + * The id of the sensor type. + */ + private final long sensorTypeId; + + /** + * The id of the method. + */ + private final long methodId; + + /** + * The only available constructor for this object. + * + * @param registrationService + * The remote object. + * @param sensorTypeId + * The id of the sensor type. + * @param methodId + * The id of the method. + */ + public AddSensorTypeToMethod(IRegistrationService registrationService, long sensorTypeId, long methodId) { + this.registrationService = registrationService; + this.sensorTypeId = sensorTypeId; + this.methodId = methodId; + } + + /** + * {@inheritDoc} + */ + protected Remote getRemoteObject() throws ServerUnavailableException { + return registrationService; + } + + /** + * {@inheritDoc} + */ + protected Object performRemoteCall(Remote remoteObject) throws RemoteException { + IRegistrationService reg = (IRegistrationService) remoteObject; + reg.addSensorTypeToMethod(sensorTypeId, methodId); + return null; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/connection/impl/AdditiveWaitRetryStrategy.java b/Agent/src/info/novatec/inspectit/agent/connection/impl/AdditiveWaitRetryStrategy.java new file mode 100644 index 000000000..b34d961c0 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/connection/impl/AdditiveWaitRetryStrategy.java @@ -0,0 +1,79 @@ +package info.novatec.inspectit.agent.connection.impl; + +import info.novatec.inspectit.agent.connection.RetryStrategy; + +/** + * The most commonly used retry strategy; it extends the waiting period by a constant amount with + * each retry. + * + * Note that the default version of this (e.g. the one with a zero argument constructor) will make 3 + * calls and wind up waiting approximately 11 seconds (zero wait for the first call, 3 seconds for + * the second call, and 8 seconds for the third call). These wait times are pretty small, and are + * usually dwarfed by socket timeouts when network difficulties occur anyway. + *

+ * IMPORTANT: The class code is copied/taken from O'REILLY onJava.com. Original + * author is William Grosso. License info can be found here. + * + * @author William Grosso + */ +public class AdditiveWaitRetryStrategy extends RetryStrategy { + + /** + * The starting wait time. + */ + public static final long STARTING_WAIT_TIME = 3000; + + /** + * Every time there is an exception, we add to the waiting time the specified one. + */ + public static final long WAIT_TIME_INCREMENT = 5000; + + /** + * The current time to wait. + */ + private long currentTimeToWait; + + /** + * The additional time to wait every time. + */ + private long waitTimeIncrement; + + /** + * The default constructor which initializes the class with the predefined values. + */ + public AdditiveWaitRetryStrategy() { + this(DEFAULT_NUMBER_OF_RETRIES, STARTING_WAIT_TIME, WAIT_TIME_INCREMENT); + } + + /** + * This constructor takes three arguments, the first is the actual number of retries till we + * completely fail. The second one is the first wait time when an error occurs, and the third + * one is the additional wait time every time when an error occurs. + * + * @param numberOfRetries + * The number of retries till it completely fails. + * @param startingWaitTime + * The starting wait time. + * @param waitTimeIncrement + * The additional wait time every time the sending etc. fails. + */ + public AdditiveWaitRetryStrategy(int numberOfRetries, long startingWaitTime, long waitTimeIncrement) { + super(numberOfRetries); + + this.currentTimeToWait = startingWaitTime; + this.waitTimeIncrement = waitTimeIncrement; + } + + /** + * {@inheritDoc} + */ + protected long getTimeToWait() { + long returnValue = currentTimeToWait; + + currentTimeToWait += waitTimeIncrement; + + return returnValue; + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/connection/impl/ExponentialBackoffRetryStrategy.java b/Agent/src/info/novatec/inspectit/agent/connection/impl/ExponentialBackoffRetryStrategy.java new file mode 100644 index 000000000..704200190 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/connection/impl/ExponentialBackoffRetryStrategy.java @@ -0,0 +1,61 @@ +package info.novatec.inspectit.agent.connection.impl; + +import info.novatec.inspectit.agent.connection.RetryStrategy; + +/** + * The classic "if it doesn't get fixed in n seconds, wait 2n seconds and try again" strategy. Using + * a large number of retries in this one results in enormously long delays. + *

+ * You probably don't want to use an ExponentialBackoffRetryStrategy in a thread which needs to be + * responsive (e.g. in the Swing event handling thread). + *

+ * IMPORTANT: The class code is copied/taken from O'REILLY onJava.com. Original + * author is William Grosso. License info can be found here. + * + * @author William Grosso + */ + +public class ExponentialBackoffRetryStrategy extends RetryStrategy { + + /** + * The starting wait time. + */ + public static final long STARTING_WAIT_TIME = 3000; + + /** + * The current time to wait for the next retry. + */ + private long currentTimeToWait; + + /** + * Default constructor which initializes the class with the default values. + */ + public ExponentialBackoffRetryStrategy() { + this(DEFAULT_NUMBER_OF_RETRIES, STARTING_WAIT_TIME); + } + + /** + * Additional constructor with two parameters. The first one defines the number of retries till + * it completely fails. The second specifies the starting wait time. + * + * @param numberOfRetries + * The number of retries. + * @param startingWaitTime + * The starting wait time. + */ + public ExponentialBackoffRetryStrategy(int numberOfRetries, long startingWaitTime) { + super(numberOfRetries); + currentTimeToWait = startingWaitTime; + } + + /** + * {@inheritDoc} + */ + protected long getTimeToWait() { + long returnValue = currentTimeToWait; + currentTimeToWait *= 2; + return returnValue; + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/connection/impl/KryoNetConnection.java b/Agent/src/info/novatec/inspectit/agent/connection/impl/KryoNetConnection.java new file mode 100644 index 000000000..0c9b541b9 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/connection/impl/KryoNetConnection.java @@ -0,0 +1,357 @@ +package info.novatec.inspectit.agent.connection.impl; + +import info.novatec.inspectit.agent.config.impl.MethodSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.PlatformSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.connection.AbstractRemoteMethodCall; +import info.novatec.inspectit.agent.connection.IConnection; +import info.novatec.inspectit.agent.connection.RegistrationException; +import info.novatec.inspectit.agent.connection.ServerUnavailableException; +import info.novatec.inspectit.agent.spring.PrototypesProvider; +import info.novatec.inspectit.cmr.service.IAgentStorageService; +import info.novatec.inspectit.cmr.service.IRegistrationService; +import info.novatec.inspectit.cmr.service.ServiceInterface; +import info.novatec.inspectit.cmr.service.exception.ServiceException; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.kryonet.Client; +import info.novatec.inspectit.kryonet.ExtendedSerializationImpl; +import info.novatec.inspectit.kryonet.IExtendedSerialization; +import info.novatec.inspectit.kryonet.rmi.ObjectSpace; +import info.novatec.inspectit.spring.logger.Log; + +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.esotericsoftware.kryonet.rmi.RemoteObject; + +/** + * Implements the {@link IConnection} interface using the kryo-net. + * + * @author Patrice Bouillet + * + */ +@Component +public class KryoNetConnection implements IConnection { + + /** + * The logger of the class. + */ + @Log + Logger log; + + /** + * {@link PrototypesProvider}. + */ + @Autowired + private PrototypesProvider prototypesProvider; + + /** + * The kryonet client to connect to the CMR. + */ + private Client client; + + /** + * The agent storage remote object which will be used to send the measurements to. + */ + private IAgentStorageService agentStorageService; + + /** + * The registration remote object which will be used for the registration of the sensors. + */ + private IRegistrationService registrationService; + + /** + * Attribute to check if we are connected. + */ + private boolean connected = false; + + /** + * Defines if there was a connection exception before. Used for throttling the info log + * messages. + */ + private boolean connectionException = false; + + /** + * The list of all network interfaces. + */ + private List networkInterfaces; + + /** + * {@inheritDoc} + */ + public void connect(String host, int port) throws ConnectException { + if (null == client) { + try { + if (!connectionException) { + log.info("KryoNet: Connecting to " + host + ":" + port); + } + initClient(host, port); + + int agentStorageServiceId = IAgentStorageService.class.getAnnotation(ServiceInterface.class).serviceId(); + agentStorageService = ObjectSpace.getRemoteObject(client, agentStorageServiceId, IAgentStorageService.class); + ((RemoteObject) agentStorageService).setNonBlocking(true); + ((RemoteObject) agentStorageService).setTransmitReturnValue(false); + + int registrationServiceServiceId = IRegistrationService.class.getAnnotation(ServiceInterface.class).serviceId(); + registrationService = ObjectSpace.getRemoteObject(client, registrationServiceServiceId, IRegistrationService.class); + ((RemoteObject) registrationService).setNonBlocking(false); + ((RemoteObject) registrationService).setTransmitReturnValue(true); + + log.info("KryoNet: Connection established!"); + connected = true; + connectionException = false; + } catch (Exception exception) { + if (!connectionException) { + log.info("KryoNet: Connection to the server failed."); + } + connectionException = true; + disconnect(); + if (log.isTraceEnabled()) { + log.trace("connect()", exception); + } + ConnectException e = new ConnectException(exception.getMessage()); + e.initCause(exception); + throw e; // NOPMD root cause exception is set + } + } + } + + /** + * Creates new client and tries to connect to host. + * + * @param host + * Host IP address. + * @param port + * Port to connect to. + * @throws Exception + * If {@link Exception} occurs during communication. + */ + private void initClient(String host, int port) throws Exception { + IExtendedSerialization serialization = new ExtendedSerializationImpl(prototypesProvider); + + client = new Client(serialization, prototypesProvider); + client.start(); + client.connect(5000, host, port); + } + + /** + * {@inheritDoc} + */ + public void disconnect() { + if (null != client) { + client.stop(); + client = null; // NOPMD + } + agentStorageService = null; // NOPMD + registrationService = null; // NOPMD + connected = false; + } + + /** + * {@inheritDoc} + */ + public long registerPlatform(String agentName, String version) throws ServerUnavailableException, RegistrationException { + if (!connected) { + throw new ServerUnavailableException(); + } + + try { + if (null == networkInterfaces) { + networkInterfaces = getNetworkInterfaces(); + } + return registrationService.registerPlatformIdent(networkInterfaces, agentName, version); + } catch (RemoteException remoteException) { + if (log.isTraceEnabled()) { + log.trace("registerPlatform(String)", remoteException); + } + throw new RegistrationException("Could not register the platform", remoteException); + } catch (SocketException socketException) { + log.error("Could not obtain network interfaces from this machine!"); + if (log.isTraceEnabled()) { + log.trace("Constructor", socketException); + } + throw new RegistrationException("Could not register the platform", socketException); + } catch (ServiceException serviceException) { + if (log.isTraceEnabled()) { + log.trace("registerPlatform(String)", serviceException); + } + throw new RegistrationException("Could not register the platform", serviceException); + } + } + + /** + * {@inheritDoc} + */ + public void unregisterPlatform(String agentName) throws RegistrationException { + if (!connected) { + return; + } + + try { + if (null == networkInterfaces) { + networkInterfaces = getNetworkInterfaces(); + } + + registrationService.unregisterPlatformIdent(networkInterfaces, agentName); + } catch (SocketException socketException) { + log.error("Could not obtain network interfaces from this machine!"); + if (log.isTraceEnabled()) { + log.trace("unregisterPlatform(List,String)", socketException); + } + throw new RegistrationException("Could not un-register the platform", socketException); + } catch (ServiceException serviceException) { + if (log.isTraceEnabled()) { + log.trace("unregisterPlatform(List,String)", serviceException); + } + throw new RegistrationException("Could not un-register the platform", serviceException); + } catch (RemoteException remoteException) { + if (log.isTraceEnabled()) { + log.trace("unregisterPlatform(List,String)", remoteException); + } + throw new RegistrationException("Could not un-register the platform", remoteException); + } + + } + + /** + * {@inheritDoc} + */ + public void sendDataObjects(List measurements) throws ServerUnavailableException { + if (!connected) { + throw new ServerUnavailableException(); + } + + if (null != measurements && !measurements.isEmpty()) { + try { + AbstractRemoteMethodCall remote = new AddDataObjects(agentStorageService, measurements); + remote.makeCall(); + } catch (ServerUnavailableException serverUnavailableException) { + if (log.isTraceEnabled()) { + log.trace("sendDataObjects(List)", serverUnavailableException); + } + throw serverUnavailableException; + } + } + } + + /** + * {@inheritDoc} + */ + public long registerMethod(long platformId, RegisteredSensorConfig sensorConfig) throws ServerUnavailableException, RegistrationException { + if (!connected) { + throw new ServerUnavailableException(); + } + + RegisterMethodIdent register = new RegisterMethodIdent(registrationService, sensorConfig, platformId); + try { + Long id = (Long) register.makeCall(); + return id.longValue(); + } catch (ServerUnavailableException serverUnavailableException) { + if (log.isTraceEnabled()) { + log.trace("registerMethod(RegisteredSensorConfig)", serverUnavailableException); + } + throw new RegistrationException("Could not register the method", serverUnavailableException); + } + + } + + /** + * {@inheritDoc} + */ + public long registerMethodSensorType(long platformId, MethodSensorTypeConfig methodSensorTypeConfig) throws ServerUnavailableException, RegistrationException { + if (!connected) { + throw new ServerUnavailableException(); + } + + RegisterMethodSensorType register = new RegisterMethodSensorType(registrationService, methodSensorTypeConfig, platformId); + try { + Long id = (Long) register.makeCall(); + return id.longValue(); + } catch (ServerUnavailableException serverUnavailableException) { + if (log.isTraceEnabled()) { + log.trace("registerMethod(RegisteredSensorConfig)", serverUnavailableException); + } + throw new RegistrationException("Could not register the method sensor type", serverUnavailableException); + } + } + + /** + * {@inheritDoc} + */ + public long registerPlatformSensorType(long platformId, PlatformSensorTypeConfig platformSensorTypeConfig) throws ServerUnavailableException, RegistrationException { + if (!connected) { + throw new ServerUnavailableException(); + } + + RegisterPlatformSensorType register = new RegisterPlatformSensorType(registrationService, platformSensorTypeConfig, platformId); + try { + Long id = (Long) register.makeCall(); + return id.longValue(); + } catch (ServerUnavailableException serverUnavailableException) { + if (log.isTraceEnabled()) { + log.trace("registerPlatformSensorType(PlatformSensorTypeConfig)", serverUnavailableException); + } + throw new RegistrationException("Could not register the platform sensor type", serverUnavailableException); + } + } + + /** + * {@inheritDoc} + */ + public void addSensorTypeToMethod(long sensorTypeId, long methodId) throws ServerUnavailableException, RegistrationException { + if (!connected) { + throw new ServerUnavailableException(); + } + + AddSensorTypeToMethod addTypeToSensor = new AddSensorTypeToMethod(registrationService, sensorTypeId, methodId); + try { + addTypeToSensor.makeCall(); + } catch (ServerUnavailableException serverUnavailableException) { + if (log.isTraceEnabled()) { + log.trace("addSensorTypeToMethod(long, long)", serverUnavailableException); + } + throw new RegistrationException("Could not add the sensor type to a method", serverUnavailableException); + } + } + + /** + * Loads all the network interfaces and transforms the enumeration to the list of strings + * containing all addresses. + * + * @return List of all network interfaces. + * @throws SocketException + * If {@link SocketException} occurs. + */ + private List getNetworkInterfaces() throws SocketException { + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + List networkInterfaces = new ArrayList(); + + while (interfaces.hasMoreElements()) { + NetworkInterface networkInterface = (NetworkInterface) interfaces.nextElement(); + Enumeration addresses = networkInterface.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress address = (InetAddress) addresses.nextElement(); + networkInterfaces.add(address.getHostAddress()); + } + } + + return networkInterfaces; + } + + /** + * {@inheritDoc} + */ + public boolean isConnected() { + return connected; + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/connection/impl/RegisterMethodIdent.java b/Agent/src/info/novatec/inspectit/agent/connection/impl/RegisterMethodIdent.java new file mode 100644 index 000000000..42ad06f06 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/connection/impl/RegisterMethodIdent.java @@ -0,0 +1,71 @@ +package info.novatec.inspectit.agent.connection.impl; + +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.connection.AbstractRemoteMethodCall; +import info.novatec.inspectit.agent.connection.ServerUnavailableException; +import info.novatec.inspectit.cmr.service.IRegistrationService; + +import java.rmi.Remote; +import java.rmi.RemoteException; + +/** + * Class which encapsulates the request to the {@link Remote} object + * {@link IRegistrationService#registerMethodIdent(long, String, String, String, java.util.List, String)} + * . + * + * @author Patrice Bouillet + * + */ +public class RegisterMethodIdent extends AbstractRemoteMethodCall { + + /** + * The registration object which is used for the actual registering. + */ + private final Remote registrationService; + + /** + * The sensor configuration which holds all the information about the sensor used for the + * registering process. + */ + private final RegisteredSensorConfig rsc; + + /** + * The ID of the current platform used for the registering process. + */ + private final long platformId; + + /** + * The only constructor for this class accepts two attributes. The first one is the + * {@link Remote} object, which will be used to send the data. The second one is the sensor + * configuration which holds the data used for the registration. + * + * @param registrationService + * The {@link Remote} object. + * @param sensorConfig + * The sensor configuration. + * @param platformId + * The ID of the platform. + */ + public RegisterMethodIdent(IRegistrationService registrationService, RegisteredSensorConfig sensorConfig, long platformId) { + this.registrationService = registrationService; + this.rsc = sensorConfig; + this.platformId = platformId; + } + + /** + * {@inheritDoc} + */ + protected Remote getRemoteObject() throws ServerUnavailableException { + return registrationService; + } + + /** + * {@inheritDoc} + */ + protected Object performRemoteCall(Remote remoteObject) throws RemoteException { + IRegistrationService reg = (IRegistrationService) remoteObject; + + return Long.valueOf(reg.registerMethodIdent(platformId, rsc.getTargetPackageName(), rsc.getTargetClassName(), rsc.getTargetMethodName(), rsc.getParameterTypes(), rsc.getReturnType(), + rsc.getModifiers())); + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/connection/impl/RegisterMethodSensorType.java b/Agent/src/info/novatec/inspectit/agent/connection/impl/RegisterMethodSensorType.java new file mode 100644 index 000000000..b7bf613ce --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/connection/impl/RegisterMethodSensorType.java @@ -0,0 +1,68 @@ +package info.novatec.inspectit.agent.connection.impl; + +import info.novatec.inspectit.agent.config.impl.MethodSensorTypeConfig; +import info.novatec.inspectit.agent.connection.AbstractRemoteMethodCall; +import info.novatec.inspectit.agent.connection.ServerUnavailableException; +import info.novatec.inspectit.cmr.service.IRegistrationService; + +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.util.Map; + +/** + * Class which encapsulates the request to the {@link Remote} object {@link IRegistrationService}. + * The method to call is + * {@link IRegistrationService#registerMethodSensorTypeIdent(long, String, Map)} + * + * @author Patrice Bouillet + * + */ +public class RegisterMethodSensorType extends AbstractRemoteMethodCall { + + /** + * The registration object which is used for the actual registering. + */ + private final Remote registrationService; + + /** + * The method sensor type configuration which is registered at the server. + */ + private final MethodSensorTypeConfig methodSensorTypeConfig; + + /** + * The ID of the current platform used for the registering process. + */ + private final long platformId; + + /** + * The only constructor for this class accepts two attributes. + * + * @param registrationService + * The {@link Remote} object. + * @param methodSensorTypeConfig + * The {@link MethodSensorTypeConfig} which is registered at the server. + * @param platformId + * The ID of the platform. + */ + public RegisterMethodSensorType(IRegistrationService registrationService, MethodSensorTypeConfig methodSensorTypeConfig, long platformId) { + this.registrationService = registrationService; + this.methodSensorTypeConfig = methodSensorTypeConfig; + this.platformId = platformId; + } + + /** + * {@inheritDoc} + */ + protected Remote getRemoteObject() throws ServerUnavailableException { + return registrationService; + } + + /** + * {@inheritDoc} + */ + protected Object performRemoteCall(Remote remoteObject) throws RemoteException { + IRegistrationService reg = (IRegistrationService) remoteObject; + + return Long.valueOf(reg.registerMethodSensorTypeIdent(platformId, methodSensorTypeConfig.getClassName(), methodSensorTypeConfig.getParameters())); + } +} \ No newline at end of file diff --git a/Agent/src/info/novatec/inspectit/agent/connection/impl/RegisterPlatformSensorType.java b/Agent/src/info/novatec/inspectit/agent/connection/impl/RegisterPlatformSensorType.java new file mode 100644 index 000000000..65fd7bf7f --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/connection/impl/RegisterPlatformSensorType.java @@ -0,0 +1,67 @@ +package info.novatec.inspectit.agent.connection.impl; + +import info.novatec.inspectit.agent.config.impl.PlatformSensorTypeConfig; +import info.novatec.inspectit.agent.connection.AbstractRemoteMethodCall; +import info.novatec.inspectit.agent.connection.ServerUnavailableException; +import info.novatec.inspectit.cmr.service.IRegistrationService; + +import java.rmi.Remote; +import java.rmi.RemoteException; + +/** + * Class which encapsulates the request to the {@link Remote} object {@link IRegistrationService}. + * The method to call is {@link IRegistrationService#registerPlatformSensorTypeIdent(long, String)}. + * + * @author Patrice Bouillet + * + */ +public class RegisterPlatformSensorType extends AbstractRemoteMethodCall { + + /** + * The registration object which is used for the actual registering. + */ + private final Remote registrationService; + + /** + * The platform sensor type configuration which is registered at the server. + */ + private final PlatformSensorTypeConfig platformSensorTypeConfig; + + /** + * The ID of the current platform used for the registering process. + */ + private final long platformId; + + /** + * The only constructor for this class accepts two attributes. + * + * @param registrationService + * The {@link Remote} object. + * @param platformSensorTypeConfig + * The {@link PlatformSensorTypeConfig} which is registered at the server. + * @param platformId + * The ID of the platform. + */ + public RegisterPlatformSensorType(IRegistrationService registrationService, PlatformSensorTypeConfig platformSensorTypeConfig, long platformId) { + this.registrationService = registrationService; + this.platformSensorTypeConfig = platformSensorTypeConfig; + this.platformId = platformId; + } + + /** + * {@inheritDoc} + */ + protected Remote getRemoteObject() throws ServerUnavailableException { + return registrationService; + } + + /** + * {@inheritDoc} + */ + protected Object performRemoteCall(Remote remoteObject) throws RemoteException { + IRegistrationService reg = (IRegistrationService) remoteObject; + + return Long.valueOf(reg.registerPlatformSensorTypeIdent(platformId, platformSensorTypeConfig.getClassName())); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/core/ICoreService.java b/Agent/src/info/novatec/inspectit/agent/core/ICoreService.java new file mode 100644 index 000000000..09ef848c0 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/core/ICoreService.java @@ -0,0 +1,145 @@ +package info.novatec.inspectit.agent.core; + +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.communication.SystemSensorData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; + +/** + * Interface definition for the core service. The core service is the central point of the Agent + * where all data is collected, triggered etc. + * + * @author Patrice Bouillet + * @author Eduard Tudenhoefner + * + */ +public interface ICoreService { + + /** + * Start this component. + */ + void start(); + + /** + * Stop this component. + */ + void stop(); + + /** + * Adds a new measurement from a method sensor to the value storage. + * + * @param sensorTypeId + * The id of the sensor type. + * @param methodId + * The id of the method. + * @param prefix + * An arbitrary prefix {@link String}. + * @param methodSensorData + * The method sensor data. + */ + void addMethodSensorData(long sensorTypeId, long methodId, String prefix, MethodSensorData methodSensorData); + + /** + * Adds a new data object from the platform sensor to the value storage. + * + * @param sensorTypeIdent + * The id of the sensor type. + * @param systemSensorData + * The system sensor data. + */ + void addPlatformSensorData(long sensorTypeIdent, SystemSensorData systemSensorData); + + /** + * Adds a new data object from the exception sensor to the value storage. + * + * @param sensorTypeIdent + * The id of the sensor type. + * @param throwableIdentityHashCode + * The identityHashCode of the {@link ExceptionSensorData} object. + * @param exceptionSensorData + * The exception sensor data. + */ + void addExceptionSensorData(long sensorTypeIdent, long throwableIdentityHashCode, ExceptionSensorData exceptionSensorData); + + /** + * Adds a new object storage to the value storage. An object storage contains an instance of + * {@link IObjectStorage} which serves as a wrapper around a value object. + * + * @param sensorTypeId + * The id of the sensor type. + * @param methodId + * The id of the method. + * @param prefix + * An arbitrary prefix {@link String}. + * @param objectStorage + * The object storage. + */ + void addObjectStorage(long sensorTypeId, long methodId, String prefix, IObjectStorage objectStorage); + + /** + * Triggers sending the buffered data. + */ + void sendData(); + + /** + * Returns a saved measurement ({@link MethodSensorData}) for further processing. + * + * @param sensorTypeIdent + * The id of the sensor type to retrieve the measurement. + * @param methodIdent + * The id of the method sensor to retrieve the measurement. + * @param prefix + * An arbitrary prefix {@link String}. + * @return Returns a {@link MethodSensorData}. + */ + MethodSensorData getMethodSensorData(long sensorTypeIdent, long methodIdent, String prefix); + + /** + * Returns a saved data object for further processing. + * + * @param sensorTypeIdent + * The id of the sensor type to retrieve the data object. + * @return Returns a {@link SystemSensorData} + */ + SystemSensorData getPlatformSensorData(long sensorTypeIdent); + + /** + * Returns a saved data object for further processing. + * + * @param sensorTypeIdent + * The id of the sensor type to retrieve the data object. + * @param throwableIdentityHashCode + * The identityHashCode of the data object to retrieve. + * @return Returns a {@link ExceptionSensorData} + */ + ExceptionSensorData getExceptionSensorData(long sensorTypeIdent, long throwableIdentityHashCode); + + /** + * Returns a saved object storage for further processing. + * + * @param sensorTypeIdent + * The id of the sensor type to retrieve the measurement. + * @param methodIdent + * The id of the method sensor to retrieve the measurement. + * @param prefix + * An arbitrary prefix {@link String}. + * @return Returns an {@link IObjectStorage}. + */ + IObjectStorage getObjectStorage(long sensorTypeIdent, long methodIdent, String prefix); + + /** + * Adds a new list listener. + * + * @param listener + * The listener to add. + */ + void addListListener(ListListener listener); + + /** + * Removes a list listener. + * + * @param listener + * The listener to remove. + */ + void removeListListener(ListListener listener); + +} diff --git a/Agent/src/info/novatec/inspectit/agent/core/IIdManager.java b/Agent/src/info/novatec/inspectit/agent/core/IIdManager.java new file mode 100644 index 000000000..7e9cf4099 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/core/IIdManager.java @@ -0,0 +1,115 @@ +package info.novatec.inspectit.agent.core; + +import info.novatec.inspectit.agent.config.impl.MethodSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.PlatformSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; + +/** + * The ID Manager is used to correlate between the local and global IDs (from the server). + * + * @author Patrice Bouillet + * @author Eduard Tudenhoefner + * + */ +public interface IIdManager { + + /** + * Starts the ID Manager so that the registration will occur after a constant amount of time. + */ + void start(); + + /** + * Stops this ID Manager, no registrations are executed any more. + */ + void stop(); + + /** + * Returns if this platform is registered with the CMR, is equal to the fact that calling the + * {@link #getPlatformId()} method results in no {@link IdNotAvailableException}. + * + * @return If this platform is registered. + */ + boolean isPlatformRegistered(); + + /** + * Returns the platform id. This is unique for this java agent in this virtual machine as long + * as a different agent name is set on the same machine. + * + * @return The unique platform id. + * @throws IdNotAvailableException + * This exception is thrown if no ID can be retrieved from this manager. + */ + long getPlatformId() throws IdNotAvailableException; + + /** + * Un-registers the platform if the agent is currently connected and registered.After calling + * this method {@link IIdManager} will omit any further calls to register the platform since it + * will assume that the shutdown of the JVM has been started. + */ + void unregisterPlatform(); + + /** + * Returns the ID of the server for this method. Needed for the data objects so they can be + * persisted properly. + * + * @param methodId + * The method ID used locally. + * @return The ID used at the server. + * @throws IdNotAvailableException + * This exception is thrown if no ID can be retrieved from this manager. + */ + long getRegisteredMethodId(long methodId) throws IdNotAvailableException; + + /** + * Returns the ID of the server for this sensor type. Needed for the data objects so they can be + * persisted properly. + * + * @param sensorTypeId + * The sensor type ID used locally. + * @return The ID used at the server. + * @throws IdNotAvailableException + * This exception is thrown if no ID can be retrieved from this manager. + */ + long getRegisteredSensorTypeId(long sensorTypeId) throws IdNotAvailableException; + + /** + * Registers a method and returns a unique id for it. + * + * @param registeredSensorConfig + * The method sensor configuration which contains all the needed information for + * registering. + * @return The unique method id. + */ + long registerMethod(RegisteredSensorConfig registeredSensorConfig); + + /** + * Registers a method sensor type and returns a unique id for it. + * + * @param methodSensorTypeConfig + * The method sensor type configuration which contains all the needed information for + * registering. + * @return The unique method sensor type id. + */ + long registerMethodSensorType(MethodSensorTypeConfig methodSensorTypeConfig); + + /** + * Adds a sensor type to a method. + * + * @param sensorTypeId + * The id of the sensor type. + * @param methodId + * The id of the method. + */ + void addSensorTypeToMethod(long sensorTypeId, long methodId); + + /** + * Registers a platform sensor type and returns a unique id for it. + * + * @param platformSensorTypeConfig + * The platform sensor type configuration which contains all the needed information + * for registering. + * @return The unique method sensor type id. + */ + long registerPlatformSensorType(PlatformSensorTypeConfig platformSensorTypeConfig); + +} diff --git a/Agent/src/info/novatec/inspectit/agent/core/IObjectStorage.java b/Agent/src/info/novatec/inspectit/agent/core/IObjectStorage.java new file mode 100644 index 000000000..570dad104 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/core/IObjectStorage.java @@ -0,0 +1,22 @@ +package info.novatec.inspectit.agent.core; + +import info.novatec.inspectit.communication.DefaultData; + +/** + * The purpose of an object storage is to handle the data from the sensors and create an appropriate + * data object which can be transmitted to the Repository. + * + * @author Patrice Bouillet + * + */ +public interface IObjectStorage { + + /** + * This method call will finish all pending actions and create the value object. Adding values + * after this method is called is not an option! + * + * @return Returns a {@link DefaultData} which can be transmitted to the Repository. + */ + DefaultData finalizeDataObject(); + +} diff --git a/Agent/src/info/novatec/inspectit/agent/core/IdNotAvailableException.java b/Agent/src/info/novatec/inspectit/agent/core/IdNotAvailableException.java new file mode 100644 index 000000000..bed3d4e9b --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/core/IdNotAvailableException.java @@ -0,0 +1,50 @@ +package info.novatec.inspectit.agent.core; + +import info.novatec.inspectit.agent.core.impl.IdManager; + +/** + * This exception is thrown whenever something happens unexpectedly while accessing or registering + * an ID (method, sensor type, agent, ...). + * + * @author Patrice Bouillet + * @see IIdManager + * @see IdManager + */ +public class IdNotAvailableException extends Exception { + + /** + * The serial version UID. + */ + private static final long serialVersionUID = -436483658552247186L; + + /** + * Constructs a new exception with the specified detail message. The cause is not initialized, + * and may subsequently be initialized by a call to {@link #initCause}. + * + * @param message + * The detail message. The detail message is saved for later retrieval by the + * {@link #getMessage()} method. + */ + public IdNotAvailableException(String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and cause. + *

+ * Note that the detail message associated with cause is not automatically + * incorporated in this exception's detail message. + * + * @param message + * the detail message (which is saved for later retrieval by the + * {@link #getMessage()} method). + * @param cause + * The cause (which is saved for later retrieval by the {@link #getCause()} method). + * (A null value is permitted, and indicates that the cause is nonexistent + * or unknown.) + */ + public IdNotAvailableException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/core/ListListener.java b/Agent/src/info/novatec/inspectit/agent/core/ListListener.java new file mode 100644 index 000000000..903f642aa --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/core/ListListener.java @@ -0,0 +1,24 @@ +package info.novatec.inspectit.agent.core; + +import java.util.EventListener; +import java.util.List; + +/** + * The {@link ListListener} interface allows a class to react on events that change a list. + * + * @param + * The content class of the list. + * + * @author Patrice Bouillet + */ +public interface ListListener extends EventListener { + + /** + * The content of a list has changed. + * + * @param list + * The list which was changed. + */ + void contentChanged(List list); + +} diff --git a/Agent/src/info/novatec/inspectit/agent/core/impl/CoreService.java b/Agent/src/info/novatec/inspectit/agent/core/impl/CoreService.java new file mode 100644 index 000000000..a2c0dc5e3 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/core/impl/CoreService.java @@ -0,0 +1,662 @@ +package info.novatec.inspectit.agent.core.impl; + +import info.novatec.inspectit.agent.buffer.IBufferStrategy; +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.config.impl.PlatformSensorTypeConfig; +import info.novatec.inspectit.agent.connection.IConnection; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IObjectStorage; +import info.novatec.inspectit.agent.core.ListListener; +import info.novatec.inspectit.agent.sending.ISendingStrategy; +import info.novatec.inspectit.agent.sensor.platform.IPlatformSensor; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.ExceptionEvent; +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.communication.SystemSensorData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.spring.logger.Log; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.DependsOn; +import org.springframework.stereotype.Component; + +/** + * Default implementation of the {@link ICoreService} interface. + * + * @author Patrice Bouillet + * @author Eduard Tudenhoefner + * + */ +@Component +@DependsOn({ "strategyAndSensorConfiguration" }) +public class CoreService implements ICoreService, InitializingBean, DisposableBean { + + /** + * The logger of the class. + */ + @Log + Logger log; + + /** + * The configuration storage. Used to access the platform sensor types. + */ + private final IConfigurationStorage configurationStorage; + + /** + * The connection to the Central Measurement Repository. + */ + private final IConnection connection; + + /** + * Id manager. + */ + private final IIdManager idManager; + + /** + * Already used data objects which can be used directly on the CMR to persist. + */ + private Map sensorDataObjects = new ConcurrentHashMap(); + + /** + * Contains object storage instances which will be initialized when sending. + */ + private Map objectStorages = new ConcurrentHashMap(); + + /** + * Used as second hash table for the measurements when processed before sending. + */ + private Map measurementsProcessing = new ConcurrentHashMap(); + + /** + * Used as second hash table for the object storages when processed before sending. + */ + private Map objectStoragesProcessing = new ConcurrentHashMap(); + + /** + * Temporary Map to switch the references of the active hash table with the processed one. + */ + private Map temp; + + /** + * The registered list listeners. + */ + private List> listListeners = new ArrayList>(); + + /** + * The available and registered sending strategies. + */ + private List sendingStrategies = new ArrayList(); + + /** + * The selected buffer strategy to store the list of value objects. + */ + private IBufferStrategy bufferStrategy; + + /** + * The default refresh time. + */ + private static final long DEFAULT_REFRESH_TIME = 1000L; + + /** + * The refresh time for the platformSensorRefresher thread in ms. + */ + private long platformSensorRefreshTime = DEFAULT_REFRESH_TIME; + + /** + * The platformSensorRefresher is a thread which updates the platform informations after the + * specified platformSensorRefreshTime. + */ + private volatile PlatformSensorRefresher platformSensorRefresher; + + /** + * The preparing thread used to execute the preparation of the measurement in a separate + * process. + */ + private volatile PreparingThread preparingThread; + + /** + * The sending thread used to execute the sending of the measurement in a separate process. + */ + private volatile SendingThread sendingThread; + + /** + * Defines if there was an exception before while trying to send the data. Used to throttle the + * printing of log statements. + */ + private boolean sendingException = false; + + /** + * The default constructor which needs 4 parameters. + * + * @param configurationStorage + * The configuration storage. + * @param connection + * The connection. + * @param bufferStrategy + * The used buffer strategy. + * @param sendingStrategies + * The {@link List} of sending strategies. + * @param idManager + * IdManager. + */ + @Autowired + public CoreService(IConfigurationStorage configurationStorage, IConnection connection, IBufferStrategy bufferStrategy, List sendingStrategies, IIdManager idManager) { + if (null == configurationStorage) { + throw new IllegalArgumentException("Configuration Storage cannot be null!"); + } + + if (null == connection) { + throw new IllegalArgumentException("Connection cannot be null!"); + } + + if (null == bufferStrategy) { + throw new IllegalArgumentException("Buffer strategy cannot be null!"); + } + + if (null == sendingStrategies || sendingStrategies.isEmpty()) { + throw new IllegalArgumentException("At least one sending strategy has to be defined!"); + } + + if (null == idManager) { + throw new IllegalArgumentException("IdManager cannot be null!"); + } + + this.configurationStorage = configurationStorage; + this.connection = connection; + this.bufferStrategy = bufferStrategy; + this.sendingStrategies = sendingStrategies; + this.idManager = idManager; + } + + /** + * {@inheritDoc} + */ + public void start() { + for (ISendingStrategy strategy : sendingStrategies) { + strategy.start(this); + } + + preparingThread = new PreparingThread(); + preparingThread.start(); + + sendingThread = new SendingThread(); + sendingThread.start(); + + platformSensorRefresher = new PlatformSensorRefresher(); + platformSensorRefresher.start(); + + Runtime.getRuntime().addShutdownHook(new ShutdownHookSender()); + } + + /** + * {@inheritDoc} + */ + public void stop() { + for (ISendingStrategy strategy : sendingStrategies) { + strategy.stop(); + } + + synchronized (preparingThread) { + preparingThread.interrupt(); + } + + synchronized (sendingThread) { + sendingThread.interrupt(); + } + + Thread temp = platformSensorRefresher; + platformSensorRefresher = null; // NOPMD + synchronized (temp) { + temp.interrupt(); + } + } + + /** + * {@inheritDoc} + */ + public void sendData() { + // notify the sending thread. if it is currently sending something, + // nothing should happen + synchronized (preparingThread) { + preparingThread.notifyAll(); + } + } + + /** + * {@inheritDoc} + */ + public void addMethodSensorData(long sensorTypeIdent, long methodIdent, String prefix, MethodSensorData methodSensorData) { + StringBuffer buffer = new StringBuffer(); + if (null != prefix) { + buffer.append(prefix); + buffer.append('.'); + } + buffer.append(methodIdent); + buffer.append('.'); + buffer.append(sensorTypeIdent); + sensorDataObjects.put(buffer.toString(), methodSensorData); + notifyListListeners(); + } + + /** + * {@inheritDoc} + */ + public MethodSensorData getMethodSensorData(long sensorTypeIdent, long methodIdent, String prefix) { + StringBuffer buffer = new StringBuffer(); + if (null != prefix) { + buffer.append(prefix); + buffer.append('.'); + } + buffer.append(methodIdent); + buffer.append('.'); + buffer.append(sensorTypeIdent); + return (MethodSensorData) sensorDataObjects.get(buffer.toString()); + } + + /** + * {@inheritDoc} + */ + public void addPlatformSensorData(long sensorTypeIdent, SystemSensorData systemSensorData) { + sensorDataObjects.put(Long.toString(sensorTypeIdent), systemSensorData); + notifyListListeners(); + } + + /** + * {@inheritDoc} + */ + public SystemSensorData getPlatformSensorData(long sensorTypeIdent) { + return (SystemSensorData) sensorDataObjects.get(Long.toString(sensorTypeIdent)); + } + + /** + * {@inheritDoc} + */ + public void addExceptionSensorData(long sensorTypeIdent, long throwableIdentityHashCode, ExceptionSensorData exceptionSensorData) { + StringBuffer buffer = new StringBuffer(); + buffer.append(sensorTypeIdent); + buffer.append("::"); + buffer.append(throwableIdentityHashCode); + String key = buffer.toString(); + + // we always only save the first data object, because this object contains the nested + // objects to create the whole exception tree + if (exceptionSensorData.getExceptionEvent().equals(ExceptionEvent.CREATED)) { + // if a data object with the same hash code was already created, then it has to be + // removed, because it was created from a constructor delegation. For us only the + // last-most data object is relevant + sensorDataObjects.put(key, exceptionSensorData); + notifyListListeners(); + } + } + + /** + * {@inheritDoc} + */ + public ExceptionSensorData getExceptionSensorData(long sensorTypeIdent, long throwableIdentityHashCode) { + StringBuffer buffer = new StringBuffer(); + buffer.append(sensorTypeIdent); + buffer.append("::"); + buffer.append(throwableIdentityHashCode); + + return (ExceptionSensorData) sensorDataObjects.get(buffer.toString()); + } + + /** + * {@inheritDoc} + */ + public void addObjectStorage(long sensorTypeIdent, long methodIdent, String prefix, IObjectStorage objectStorage) { + StringBuffer buffer = new StringBuffer(); + if (null != prefix) { + buffer.append(prefix); + buffer.append('.'); + } + buffer.append(methodIdent); + buffer.append('.'); + buffer.append(sensorTypeIdent); + objectStorages.put(buffer.toString(), objectStorage); + notifyListListeners(); + } + + /** + * {@inheritDoc} + */ + public IObjectStorage getObjectStorage(long sensorTypeIdent, long methodIdent, String prefix) { + StringBuffer buffer = new StringBuffer(); + if (null != prefix) { + buffer.append(prefix); + buffer.append('.'); + } + buffer.append(methodIdent); + buffer.append('.'); + buffer.append(sensorTypeIdent); + return (IObjectStorage) objectStorages.get(buffer.toString()); + } + + /** + * {@inheritDoc} + */ + public void addListListener(ListListener listener) { + if (!listListeners.contains(listener)) { + listListeners.add(listener); + } + } + + /** + * {@inheritDoc} + */ + public void removeListListener(ListListener listener) { + listListeners.remove(listener); + } + + /** + * Notify all registered listeners that a change occurred in the lists. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void notifyListListeners() { + if (!listListeners.isEmpty()) { + List temp = new ArrayList(sensorDataObjects.values()); + temp.addAll(objectStorages.values()); + for (ListListener listListener : listListeners) { + listListener.contentChanged(temp); + } + } + } + + /** + * The PlatformSensorRefresher is a {@link Thread} which waits the specified + * platformSensorRefreshTime and then updates the platform informations. + * + * @author Eduard Tudenhoefner + * + */ + private class PlatformSensorRefresher extends Thread { + + /** + * Creates a new instance of the PlatformSensorRefresher as a daemon thread. + */ + public PlatformSensorRefresher() { + setName("inspectit-platform-sensor-refresher-thread"); + setDaemon(true); + } + + /** + * {@inheritDoc} + */ + public void run() { + Thread thisThread = Thread.currentThread(); + while (platformSensorRefresher == thisThread) { // NOPMD + try { + synchronized (this) { + wait(platformSensorRefreshTime); + } + } catch (InterruptedException e) { + log.error("Platform sensor refresher was interrupted!"); + } + + // iterate the platformSensors and update the information + for (PlatformSensorTypeConfig platformSensorTypeConfig : configurationStorage.getPlatformSensorTypes()) { + IPlatformSensor platformSensor = (IPlatformSensor) platformSensorTypeConfig.getSensorType(); + if (platformSensor.automaticUpdate()) { + platformSensor.update(CoreService.this, platformSensorTypeConfig.getId()); + } + } + } + } + } + + /** + * Returns the current refresh time of the platform sensors. + * + * @return The platform sensor refresh time. + */ + public long getPlatformSensorRefreshTime() { + return platformSensorRefreshTime; + } + + /** + * Sets the platform sensor refresh time. + * + * @param platformSensorRefreshTime + * The platform sensor refresh time to set. + */ + public void setPlatformSensorRefreshTime(long platformSensorRefreshTime) { + this.platformSensorRefreshTime = platformSensorRefreshTime; + } + + /** + * Prepares collected data for sending. + * + * Get all the value objects from the object storages and generate a list containing all the + * value objects. + * + * WARNING: This code is supposed to be run single-threaded! We ensure single-threaded + * invocation by only calling this method within the single PreparingThread. During + * the JVM shutdown (in the shutdownhook), it is also ensured that this code is run + * singlethreaded. + * + * @return true if new data were prepared, else false + */ + @SuppressWarnings("unchecked") + private boolean prepareData() { + // check if measurements are added in the last interval, if not + // nothing needs to be sent. + if (sensorDataObjects.isEmpty() && objectStorages.isEmpty()) { + return false; + } + + // switch the references so that new data is stored + // while sending + temp = sensorDataObjects; + sensorDataObjects = measurementsProcessing; + measurementsProcessing = (Map) temp; + + temp = objectStorages; + objectStorages = objectStoragesProcessing; + objectStoragesProcessing = (Map) temp; + + // copy the measurements values to a new list + List tempList = new ArrayList(measurementsProcessing.values()); + measurementsProcessing.clear(); + + // iterate the object storages and get the value + // objects which will be stored in the same list. + for (Iterator i = objectStoragesProcessing.values().iterator(); i.hasNext();) { + IObjectStorage objectStorage = i.next(); + tempList.add(objectStorage.finalizeDataObject()); + } + objectStoragesProcessing.clear(); + + // Now give the strategy the list + bufferStrategy.addMeasurements(tempList); + + return true; + } + + /** + * sends the data. + * + * WARNING: This code is supposed to be run single-threaded! We ensure single-threaded + * invocation by only calling this method within the single SendingThread. During + * the JVM shutdown (in the shutdownhook), it is also ensured that this code is run + * singlethreaded. + */ + private void send() { + try { + while (bufferStrategy.hasNext()) { + List dataToSend = bufferStrategy.next(); + connection.sendDataObjects(dataToSend); + sendingException = false; + } + } catch (Throwable e) { // NOPMD NOCHK + if (!sendingException) { + sendingException = true; + log.error("Connection problem appeared, stopping sending actual data!", e); + } + } + } + + /** + * This implementation of a {@link Thread} is used to prepare the data and value objects that + * have to be sent to the CMR. Prepared data is put into {@link IBufferStrategy}. + *

+ * Note that only one thread of this type can be started. Otherwise serious synchronization + * problems can appear. + * + * @author Patrice Bouillet + * @author Ivan Senic + * @author Stefan Siegl + */ + private class PreparingThread extends Thread { + + /** + * Creates a new PreparingThread as daemon. + */ + public PreparingThread() { + setName("inspectit-preparing-thread"); + setDaemon(true); + } + + /** + * {@inheritDoc} + */ + public void run() { + while (!isInterrupted()) { + // wait for activation + synchronized (this) { + try { + if (!isInterrupted()) { + wait(); + } + } catch (InterruptedException e) { + log.error("Preparing thread interrupted and shutting down!"); + break; // we were interrupted during waiting and close ourself down. + } + } + + // We got a request from one of the send strategies. + + boolean newDataAvailable = prepareData(); + if (newDataAvailable) { + // Notify sending thread + synchronized (sendingThread) { + sendingThread.notifyAll(); + } + } + } + } + } + + /** + * This implementation of a {@link Thread} is taking the data from the {@link IBufferStrategy} + * and sending it to the CMR. + *

+ * Note that only one thread of this type can be started. Otherwise serious synchronization + * problems can appear. + * + * @author Ivan Senic + * @author Stefan Siegl + */ + private class SendingThread extends Thread { + + /** + * Creates a new SendingThread as daemon. + */ + public SendingThread() { + setName("inspectit-sending-thread"); + setDaemon(true); + } + + /** + * {@inheritDoc} + */ + public void run() { + while (!isInterrupted()) { + // wait for activation if there is nothing to send + if (!bufferStrategy.hasNext()) { + synchronized (this) { + try { + if (!isInterrupted()) { + wait(); + } + } catch (InterruptedException e) { + log.error("Sending thread interrupted and shuting down!"); + break; // we were interrupted during waiting and close ourself down. + } + } + } + + // send the data + send(); + } + } + } + + /** + * Used for the JVM Shutdown. Ensure that all threads are closed correctly and tries to send + * data one last time to prevent data loss. + * + * @author Stefan Siegl + */ + private class ShutdownHookSender extends Thread { + @Override + public void run() { + log.info("Shutdown initialized, sending remaining data"); + // Stop the CoreService services + CoreService.this.stop(); + + // wait for the shutdown of the preparing thread and sending thread to ensure thread + // safety on the entities used for preparing and sending. If we get interrupted while + // waiting, then we stop the ShutdownHook completely. We'll wait only 10 seconds as + // a maximum for each join and then continue + try { + preparingThread.join(10000); + } catch (InterruptedException e) { + log.error("ShutdownHook was interrupted while waiting for the preparing thread to shut down. Stopping the shutdown hook"); + return; + } + + try { + sendingThread.join(10000); + } catch (InterruptedException e) { + log.error("ShutdownHook was interrupted while waiting for the sending thread to shut down. Stopping the shutdown hook"); + return; + } + + // Try to prepare data for the last time. + CoreService.this.prepareData(); + + // Try to send data for the last time. We do not set a timeout here, the user can simply + // kill the process for good if it takes too long. + CoreService.this.send(); + + // At the end unregister platform + log.info("Unregistering the Agent"); + idManager.unregisterPlatform(); + } + } + + /** + * {@inheritDoc} + */ + public void afterPropertiesSet() throws Exception { + start(); + } + + /** + * {@inheritDoc} + */ + public void destroy() throws Exception { + stop(); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/core/impl/IdManager.java b/Agent/src/info/novatec/inspectit/agent/core/impl/IdManager.java new file mode 100644 index 000000000..70999eab6 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/core/impl/IdManager.java @@ -0,0 +1,742 @@ +package info.novatec.inspectit.agent.core.impl; + +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.config.impl.AbstractSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.MethodSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.PlatformSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.config.impl.RepositoryConfig; +import info.novatec.inspectit.agent.connection.IConnection; +import info.novatec.inspectit.agent.connection.RegistrationException; +import info.novatec.inspectit.agent.connection.ServerUnavailableException; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.spring.logger.Log; +import info.novatec.inspectit.versioning.IVersioningService; + +import java.io.IOException; +import java.net.ConnectException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import org.slf4j.Logger; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * The default implementation of the ID Manager. + * + * @author Patrice Bouillet + * @author Eduard Tudenhoefner + * + */ +@Component +public class IdManager implements IIdManager, InitializingBean, DisposableBean { + + /** + * The logger of the class. + */ + @Log + Logger log; + + /** + * The configuration storage used to access some information which needs to be registered at the + * server. + */ + private final IConfigurationStorage configurationStorage; + + /** + * The versioning service. + */ + private final IVersioningService versioningService; + + /** + * The connection to the Central Measurement Repository. + */ + private final IConnection connection; + + /** + * The id of this platform. + */ + private long platformId = -1; + + /** + * The mapping between the local and remote method ids. + */ + private Map methodIdMap = new HashMap(); + + /** + * The mapping between the local and remote sensor type ids. + */ + private Map sensorTypeIdMap = new HashMap(); + + /** + * The {@link Thread} used to register the outstanding methods, sensor types etc. + */ + private volatile RegistrationThread registrationThread; + + /** + * The methods to register at the server. + */ + private LinkedList methodsToRegister = new LinkedList(); // NOPMD + + /** + * The sensor types to register at the server. + */ + private LinkedList sensorTypesToRegister = new LinkedList(); // NOPMD + + /** + * The mapping between the sensor types and methods to register at the server. + */ + private LinkedList sensorTypeToMethodRegister = new LinkedList(); // NOPMD + + /** + * If set to true, the connection to server created an exception. + */ + private volatile boolean serverErrorOccured = false; + + /** + * If set to true any attempt to get the platform id will fail cause the underlying + * JVM is shutting down. + */ + private volatile boolean shutdownInitialized = false; + + /** + * Default constructor. Needs an implementation of the {@link IConnection} interface to + * establish the connection to the server. + * + * @param configurationStorage + * The configuration storage. + * @param connection + * The connection to the server. + * @param versioning + * The versioning service. + */ + @Autowired + public IdManager(IConfigurationStorage configurationStorage, IConnection connection, IVersioningService versioning) { + this.configurationStorage = configurationStorage; + this.connection = connection; + this.versioningService = versioning; + } + + /** + * {@inheritDoc} + */ + public void start() { + if (null == registrationThread) { + registrationThread = new RegistrationThread(); + registrationThread.start(); + } + + // register all method sensor types saved in the configuration storage + for (MethodSensorTypeConfig config : configurationStorage.getMethodSensorTypes()) { + this.registerMethodSensorType(config); + } + + // register all platform sensor types saved in the configuration storage + for (PlatformSensorTypeConfig config : configurationStorage.getPlatformSensorTypes()) { + this.registerPlatformSensorType(config); + } + } + + /** + * {@inheritDoc} + */ + public void stop() { + // set the registration thread to null to indicate that the while loop + // will be finished on the next run. + Thread temp = registrationThread; + registrationThread = null; // NOPMD + synchronized (temp) { + temp.interrupt(); + } + } + + /** + * {@inheritDoc} + */ + public boolean isPlatformRegistered() { + return -1 != platformId; + } + + /** + * {@inheritDoc} + */ + public long getPlatformId() throws IdNotAvailableException { + // if we are not connected to the server and the platform id was not + // received yet we are throwing an IdNotAvailableException + if (!connection.isConnected() && !isPlatformRegistered() && !shutdownInitialized) { + if (!serverErrorOccured) { + try { + registrationThread.connect(); + registrationThread.registerPlatform(); + } catch (Throwable throwable) { // NOPMD + serverErrorOccured = true; + throw new IdNotAvailableException("Connection is not established yet, cannot retrieve platform ID", throwable); + } + } else { + throw new IdNotAvailableException("Cannot retrieve platform ID"); + } + } else if (!isPlatformRegistered() && !shutdownInitialized) { + if (!serverErrorOccured) { + // If the platform is not registered and no server error + // occurred, the registration is started + try { + registrationThread.registerPlatform(); + } catch (Throwable throwable) { // NOPMD + serverErrorOccured = true; + log.warn("Could not register the platform even though the connection seems to be established, will try later!"); + throw new IdNotAvailableException("Could not register the platform even though the connection seems to be established, will try later!", throwable); + } + } else { + throw new IdNotAvailableException("Cannot retrieve platform ID"); + } + } else if (shutdownInitialized) { + throw new IdNotAvailableException("Cannot retrieve platform ID because the shutdown has been initialized."); + } + + return platformId; + } + + /** + * {@inheritDoc} + */ + public void unregisterPlatform() { + this.shutdownInitialized = true; + if (connection.isConnected() && isPlatformRegistered()) { + try { + connection.unregisterPlatform(configurationStorage.getAgentName()); + platformId = -1; + } catch (Throwable e) { // NOPMD + log.warn("Could not un-register the platform."); + } + } + } + + /** + * {@inheritDoc} + */ + public long getRegisteredMethodId(long methodId) throws IdNotAvailableException { + Long methodIdentifier = Long.valueOf(methodId); + + // do not enter the block if the method ID map already contains this + // identifier (which means that it is already registered). + if (!methodIdMap.containsKey(methodIdentifier)) { + throw new IdNotAvailableException("Method ID '" + methodId + "' is not mapped"); + } else { + Long registeredMethodIdentifier = (Long) methodIdMap.get(methodIdentifier); + return registeredMethodIdentifier.longValue(); + } + } + + /** + * {@inheritDoc} + */ + public long getRegisteredSensorTypeId(long sensorTypeId) throws IdNotAvailableException { + // same procedure here as in the #getRegisteredMethodId(...) method. + Long sensorTypeIdentifier = Long.valueOf(sensorTypeId); + + if (!sensorTypeIdMap.containsKey(sensorTypeIdentifier)) { + throw new IdNotAvailableException("Sensor Type ID '" + sensorTypeId + "' is not mapped"); + } else { + Long registeredSensorTypeIdentifier = (Long) sensorTypeIdMap.get(sensorTypeIdentifier); + return registeredSensorTypeIdentifier.longValue(); + } + } + + /** + * {@inheritDoc} + */ + public long registerMethod(RegisteredSensorConfig registeredSensorConfig) { + long id; + synchronized (methodsToRegister) { + id = methodIdMap.size() + methodsToRegister.size(); + } + registeredSensorConfig.setId(id); + + if (!serverErrorOccured) { + try { + if (!isPlatformRegistered()) { + getPlatformId(); + } + + registrationThread.registerMethod(registeredSensorConfig); + } catch (Throwable throwable) { // NOPMD + synchronized (methodsToRegister) { + methodsToRegister.addLast(registeredSensorConfig); + + // start the thread to retry the registration + synchronized (registrationThread) { + registrationThread.notifyAll(); + } + } + } + } else { + synchronized (methodsToRegister) { + methodsToRegister.addLast(registeredSensorConfig); + } + } + + return id; + } + + /** + * {@inheritDoc} + */ + public long registerMethodSensorType(MethodSensorTypeConfig methodSensorTypeConfig) { + // same procedure here as in #registerMethod(...) + long id; + synchronized (sensorTypesToRegister) { + id = sensorTypeIdMap.size() + sensorTypesToRegister.size(); + } + methodSensorTypeConfig.setId(id); + + if (!serverErrorOccured) { + try { + if (!isPlatformRegistered()) { + getPlatformId(); + } + + registrationThread.registerSensorType(methodSensorTypeConfig); + } catch (Throwable throwable) { // NOPMD + synchronized (sensorTypesToRegister) { + sensorTypesToRegister.addLast(methodSensorTypeConfig); + + // start the thread to retry the registration + synchronized (registrationThread) { + registrationThread.notifyAll(); + } + } + } + } else { + synchronized (sensorTypesToRegister) { + sensorTypesToRegister.addLast(methodSensorTypeConfig); + } + } + + return id; + } + + /** + * {@inheritDoc} + */ + public void addSensorTypeToMethod(long sensorTypeId, long methodId) { + // nearly same procedure as in #registerMethod(...) but without + // returning a value. This mapping only needs to be registered. + + if (!serverErrorOccured) { + try { + if (!isPlatformRegistered()) { + getPlatformId(); + } + + registrationThread.addSensorTypeToMethod(Long.valueOf(sensorTypeId), Long.valueOf(methodId)); + } catch (Throwable throwable) { // NOPMD + synchronized (sensorTypeToMethodRegister) { + sensorTypeToMethodRegister.addLast(new SensorTypeToMethodMapping(sensorTypeId, methodId)); + } + + // start the thread to retry the registration + synchronized (registrationThread) { + registrationThread.notifyAll(); + } + } + } else { + synchronized (sensorTypeToMethodRegister) { + sensorTypeToMethodRegister.addLast(new SensorTypeToMethodMapping(sensorTypeId, methodId)); + } + } + } + + /** + * {@inheritDoc} + */ + public long registerPlatformSensorType(PlatformSensorTypeConfig platformSensorTypeConfig) { + // same procedure here as in #registerMethod(...) + long id; + synchronized (sensorTypesToRegister) { + id = sensorTypeIdMap.size() + sensorTypesToRegister.size(); + } + platformSensorTypeConfig.setId(id); + + if (!serverErrorOccured) { + try { + if (!isPlatformRegistered()) { + getPlatformId(); + } + + registrationThread.registerSensorType(platformSensorTypeConfig); + } catch (Throwable throwable) { // NOPMD + synchronized (sensorTypesToRegister) { + sensorTypesToRegister.addLast(platformSensorTypeConfig); + + // start the thread to retry the registration + synchronized (registrationThread) { + registrationThread.notifyAll(); + } + } + } + } else { + synchronized (sensorTypesToRegister) { + sensorTypesToRegister.addLast(platformSensorTypeConfig); + } + } + + return id; + } + + /** + * Private inner class used to store the mapping between the sensor type IDs and the method IDs. + * Only used if they are not yet registered. + * + * @author Patrice Bouillet + * + */ + private static class SensorTypeToMethodMapping { + + /** + * The sensor type identifier. + */ + private long sensorTypeId; + + /** + * The method identifier. + */ + private long methodId; + + /** + * Creates a new instance. + * + * @param sensorTypeId + * the sensor type id. + * @param methodId + * the method id. + */ + public SensorTypeToMethodMapping(long sensorTypeId, long methodId) { + this.sensorTypeId = sensorTypeId; + this.methodId = methodId; + } + + public long getSensorTypeId() { + return sensorTypeId; + } + + public long getMethodId() { + return methodId; + } + + } + + /** + * The {@link Thread} used to register the outstanding methods, sensor types etc. + * + * @author Patrice Bouillet + * + */ + private class RegistrationThread extends Thread { + + /** + * The default wait time between the registrations. + */ + private static final long REGISTRATION_WAIT_TIME = 10000L; + + /** + * Creates a new instance of the RegistrationThread as a daemon thread. + */ + public RegistrationThread() { + setName("inspectit-registration-thread"); + setDaemon(true); + } + + /** + * {@inheritDoc} + */ + public void run() { + Thread thisThread = Thread.currentThread(); + // break out of the while loop if the registrationThread is set + // to null in the stop method of the surrounding class. + while (registrationThread == thisThread) { // NOPMD + try { + synchronized (this) { + if (serverErrorOccured) { + // wait for the given time till we try the + // registering again. + wait(REGISTRATION_WAIT_TIME); + } else if (methodsToRegister.isEmpty() && sensorTypesToRegister.isEmpty() && sensorTypeToMethodRegister.isEmpty()) { + // Wait for a Object#notify() + wait(); + } + } + } catch (InterruptedException e) { // NOCHK + // nothing to do + } + + doRegistration(); + } + } + + /** + * Execute the registration if needed. + */ + private void doRegistration() { + try { + // not connected? -> connect + if (!connection.isConnected()) { + connect(); + } + + // register the agent + if (!isPlatformRegistered()) { + registerPlatform(); + } + + registerMethods(); + registerSensorTypes(); + registerSensorTypeToMethodMapping(); + + // clear the flag + serverErrorOccured = false; + } catch (ServerUnavailableException serverUnavailableException) { + if (!serverErrorOccured) { + log.error("Server unavailable while trying to register something at the server."); + } + serverErrorOccured = true; + if (log.isTraceEnabled()) { + log.trace("run()", serverUnavailableException); + } + } catch (RegistrationException registrationException) { + if (!serverErrorOccured) { + log.error("Registration exception occurred while trying to register something at the server."); + } + serverErrorOccured = true; + if (log.isTraceEnabled()) { + log.trace("run()", registrationException); + } + } catch (ConnectException connectException) { + if (!serverErrorOccured) { + log.error("Connection to the server failed."); + } + serverErrorOccured = true; + if (log.isTraceEnabled()) { + log.trace("run()", connectException); + } + } + } + + /** + * Establish the connection to the server. + * + * @exception ConnectException + * Throws a ConnectException if there was a problem connecting to the + * repository. + */ + private void connect() throws ConnectException { + RepositoryConfig repositoryConfig = configurationStorage.getRepositoryConfig(); + connection.connect(repositoryConfig.getHost(), repositoryConfig.getPort()); + } + + /** + * Registers the platform at the CMR. + * + * @throws ServerUnavailableException + * If the sending wasn't successful in any way, a + * {@link ServerUnavailableException} exception is thrown. + * @throws RegistrationException + * This exception is thrown when a problem with the registration process + * appears. + */ + private void registerPlatform() throws ServerUnavailableException, RegistrationException { + platformId = connection.registerPlatform(configurationStorage.getAgentName(), getVersion()); + + if (log.isDebugEnabled()) { + log.debug("Received platform ID: " + platformId); + } + } + + /** + * Returns the formatted version. + * + * @return the formatted version. + */ + private String getVersion() { + try { + return versioningService.getVersion(); + } catch (IOException e) { + if (log.isDebugEnabled()) { + log.debug("Version information could not be read", e); + } + return "n/a"; + } + } + + /** + * Registers all sensor type to method mappings on the server. + * + * @throws ServerUnavailableException + * Thrown if a server error occurred. + * @throws RegistrationException + * Thrown if something happened while trying to register the mapping on the + * server. + */ + private void registerSensorTypeToMethodMapping() throws ServerUnavailableException, RegistrationException { + while (!sensorTypeToMethodRegister.isEmpty()) { + SensorTypeToMethodMapping mapping; + mapping = (SensorTypeToMethodMapping) sensorTypeToMethodRegister.getFirst(); + + Long sensorTypeId = Long.valueOf(mapping.getSensorTypeId()); + Long methodId = Long.valueOf(mapping.getMethodId()); + + this.addSensorTypeToMethod(sensorTypeId, methodId); + synchronized (sensorTypeToMethodRegister) { + sensorTypeToMethodRegister.removeFirst(); + } + } + } + + /** + * Registers the mapping between the sensor type and a method. + * + * @param sensorTypeId + * The sensor type id. + * @param methodId + * The method id. + * @throws ServerUnavailableException + * Thrown if a server error occurred. + * @throws RegistrationException + * Thrown if something happened while trying to register the mapping on the + * server. + */ + private void addSensorTypeToMethod(Long sensorTypeId, Long methodId) throws ServerUnavailableException, RegistrationException { + if (!sensorTypeIdMap.containsKey(sensorTypeId)) { + throw new RegistrationException("Sensor type ID could not be found in the map!"); + } + + if (!methodIdMap.containsKey(methodId)) { + throw new RegistrationException("Method ID could not be found in the map!"); + } + + Long serverSensorTypeId = (Long) sensorTypeIdMap.get(sensorTypeId); + Long serverMethodId = (Long) methodIdMap.get(methodId); + + connection.addSensorTypeToMethod(serverSensorTypeId.longValue(), serverMethodId.longValue()); + + if (log.isDebugEnabled()) { + log.debug("Mapping registered (method -> sensor type) :: local:" + methodId + "->" + sensorTypeId + " global:" + serverMethodId + "->" + serverSensorTypeId); + } + } + + /** + * Registers all sensor types on the server. + * + * @throws ServerUnavailableException + * Thrown if a server error occured. + * @throws RegistrationException + * Thrown if something happened while trying to register the sensor types on the + * server. + */ + private void registerSensorTypes() throws ServerUnavailableException, RegistrationException { + while (!sensorTypesToRegister.isEmpty()) { + AbstractSensorTypeConfig astc = sensorTypesToRegister.getFirst(); + + this.registerSensorType(astc); + synchronized (sensorTypesToRegister) { + sensorTypesToRegister.removeFirst(); + } + } + } + + /** + * Registers a sensor type configuration at the server. Accepts + * {@link MethodSensorTypeConfig} and {@link PlatformSensorTypeConfig} objects. + * + * @param astc + * The sensor type configuration. + * @throws ServerUnavailableException + * Thrown if a server error occurred. + * @throws RegistrationException + * Thrown if something happened while trying to register the sensor types on the + * server. + */ + private void registerSensorType(AbstractSensorTypeConfig astc) throws ServerUnavailableException, RegistrationException { + long registeredId; + if (astc instanceof MethodSensorTypeConfig) { + registeredId = connection.registerMethodSensorType(platformId, (MethodSensorTypeConfig) astc); + } else if (astc instanceof PlatformSensorTypeConfig) { + registeredId = connection.registerPlatformSensorType(platformId, (PlatformSensorTypeConfig) astc); + } else { + throw new RegistrationException("Could not register sensor type, because unhandled type: " + astc.getClass().getName()); + } + + synchronized (sensorTypesToRegister) { + Long localId = Long.valueOf(sensorTypeIdMap.size()); + sensorTypeIdMap.put(localId, Long.valueOf(registeredId)); + + if (log.isDebugEnabled()) { + log.debug("Sensor type " + astc.toString() + " registered. ID (local/global): " + localId + "/" + registeredId); + } + } + } + + /** + * Registers all methods on the server. + * + * @throws ServerUnavailableException + * Thrown if a server error occurred. + * @throws RegistrationException + * Thrown if something happened while trying to register the sensor types on the + * server. + */ + private void registerMethods() throws ServerUnavailableException, RegistrationException { + while (!methodsToRegister.isEmpty()) { + RegisteredSensorConfig rsc; + rsc = (RegisteredSensorConfig) methodsToRegister.getFirst(); + this.registerMethod(rsc); + synchronized (methodsToRegister) { + methodsToRegister.removeFirst(); + } + } + } + + /** + * Registers a method on the server and maps the local and global id. + * + * @param rsc + * The {@link RegisteredSensorConfig} to be registered at the server. + * @throws ServerUnavailableException + * Thrown if a server error occurred. + * @throws RegistrationException + * Thrown if something happened while trying to register the sensor types on the + * server. + */ + private void registerMethod(RegisteredSensorConfig rsc) throws ServerUnavailableException, RegistrationException { + long registeredId = connection.registerMethod(platformId, rsc); + synchronized (methodsToRegister) { + Long localId = Long.valueOf(methodIdMap.size()); + methodIdMap.put(localId, Long.valueOf(registeredId)); + + if (log.isDebugEnabled()) { + log.debug("Method " + rsc.toString() + " registered. ID (local/global): " + localId + "/" + registeredId); + } + } + } + + } + + /** + * {@inheritDoc} + */ + public void afterPropertiesSet() throws Exception { + start(); + } + + /** + * {@inheritDoc} + */ + public void destroy() throws Exception { + stop(); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/hooking/IConstructorHook.java b/Agent/src/info/novatec/inspectit/agent/hooking/IConstructorHook.java new file mode 100644 index 000000000..eb7f94d4c --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/hooking/IConstructorHook.java @@ -0,0 +1,50 @@ +package info.novatec.inspectit.agent.hooking; + +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; + +/** + * Classes which add a hook into a constructor have to implement this interface. + * + */ +public interface IConstructorHook extends IHook { + + /** + * The bytecode is inserted before a constructor in the super class or this class is called. + * Therefore, the inserted bytecode is subject to constraints described in Section 4.8.2 of The + * Java Virtual Machine Specification (2nd ed). For example, it cannot access instance fields or + * methods although it may assign a value to an instance field directly declared in this class. + * Accessing static fields and methods is allowed. + * + * @param methodId + * The unique method id. + * @param sensorTypeId + * The unique sensor type id. + * @param parameters + * The array of parameters. + * @param rsc + * The {@link RegisteredSensorConfig} object which holds all the information of the + * executed method. + */ + void beforeConstructor(long methodId, long sensorTypeId, Object[] parameters, RegisteredSensorConfig rsc); + + /** + * The bytecode is inserted after the constructor calls. + * + * @param coreService + * The core service. + * @param methodId + * The unique method id. + * @param sensorTypeId + * The unique sensor type id. + * @param object + * The class itself which contains the hook. + * @param parameters + * The array of parameters. + * @param rsc + * The {@link RegisteredSensorConfig} object which holds all the information of the + * executed method. + */ + void afterConstructor(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc); + +} diff --git a/Agent/src/info/novatec/inspectit/agent/hooking/IHook.java b/Agent/src/info/novatec/inspectit/agent/hooking/IHook.java new file mode 100644 index 000000000..09e1b7866 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/hooking/IHook.java @@ -0,0 +1,9 @@ +package info.novatec.inspectit.agent.hooking; + +/** + * A marker interface to know that the class which implements this interface contains hooks. + * + * @author Patrice Bouillet + */ +public interface IHook { +} diff --git a/Agent/src/info/novatec/inspectit/agent/hooking/IHookDispatcher.java b/Agent/src/info/novatec/inspectit/agent/hooking/IHookDispatcher.java new file mode 100644 index 000000000..0d143bc62 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/hooking/IHookDispatcher.java @@ -0,0 +1,123 @@ +package info.novatec.inspectit.agent.hooking; + +/** + * The hook dispatcher interface defines methods to add method and constructor mappings and methods + * to dispatch the calls from the instrumented methods in the target application. + * + * @author Patrice Bouillet + * @author Eduard Tudenhoefner + * + */ +public interface IHookDispatcher { + + /** + * Dispatches the 'before' method statement. + * + * @param id + * The id of the method. + * @param object + * The instance of the class or the class itself. + * @param parameters + * The parameters of the method. + */ + void dispatchMethodBeforeBody(long id, Object object, Object[] parameters); + + /** + * Dispatches the first 'after' method statement. + * + * @param id + * The id of the method. + * @param object + * The instance of the class or the class itself. + * @param parameters + * The parameters of the method. + * @param returnValue + * The return value of the method. + */ + void dispatchFirstMethodAfterBody(long id, Object object, Object[] parameters, Object returnValue); + + /** + * Dispatches the second 'after' method statement. + * + * @param id + * The id of the method. + * @param object + * The instance of the class or the class itself. + * @param parameters + * The parameters of the method. + * @param returnValue + * The return value of the method. + */ + void dispatchSecondMethodAfterBody(long id, Object object, Object[] parameters, Object returnValue); + + /** + * Dispatches the 'addCatch' statement of a method. + * + * @param id + * The id of the method. + * @param object + * The instance of the class. + * @param parameters + * The parameters of the method. + * @param exceptionObject + * The instance of the {@link Throwable} object. + */ + void dispatchOnThrowInBody(long id, Object object, Object[] parameters, Object exceptionObject); + + /** + * Dispatches the handler of a {@link Throwable}. + * + * @param id + * The id of the method where the {@link Throwable} is handled. + * @param exceptionObject + * The instance of the {@link Throwable} object. + */ + void dispatchBeforeCatch(long id, Object exceptionObject); + + /** + * Dispatches the 'addCatch' statement of a constructor. + * + * @param id + * The id of the method. + * @param object + * The instance of the class. + * @param parameters + * The parameters of the constructor. + * @param exceptionObject + * The instance of the {@link Throwable} object. + */ + void dispatchConstructorOnThrowInBody(long id, Object object, Object[] parameters, Object exceptionObject); + + /** + * Dispatches the handler of a {@link Throwable}. + * + * @param id + * The id of the constructor where the {@link Throwable} is handled. + * @param exceptionObject + * The instance of the {@link Throwable} object. + */ + void dispatchConstructorBeforeCatch(long id, Object exceptionObject); + + /** + * Dispatches the 'before' constructor statement. + * + * @param id + * The id of the method. + * @param parameters + * The parameters of the method. + */ + void dispatchConstructorBeforeBody(long id, Object[] parameters); + + /** + * Dispatches the 'after' constructor statement. + * + * @param id + * The id of the method. + * @param object + * The instance of the class or the class itself. + * @param parameters + * The parameters of the method. + */ + void dispatchConstructorAfterBody(long id, Object object, Object[] parameters); + +} \ No newline at end of file diff --git a/Agent/src/info/novatec/inspectit/agent/hooking/IHookDispatcherMapper.java b/Agent/src/info/novatec/inspectit/agent/hooking/IHookDispatcherMapper.java new file mode 100644 index 000000000..02e7bc5aa --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/hooking/IHookDispatcherMapper.java @@ -0,0 +1,33 @@ +package info.novatec.inspectit.agent.hooking; + +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; + +/** + * Extended version of the {@link IHookDispatcher} to over come problems of loading the + * {@link RegisteredSensorConfig} class. + * + * @author Ivan Senic + * + */ +public interface IHookDispatcherMapper { + + /** + * Adds a method mapping to the dispatcher. + * + * @param id + * The id of the mapping. + * @param rsc + * The {@link RegisteredSensorConfig} object of this mapping. + */ + void addMethodMapping(long id, RegisteredSensorConfig rsc); + + /** + * Adds a constructor mapping to the dispatcher. + * + * @param id + * The id of the mapping. + * @param rsc + * The {@link RegisteredSensorConfig} object of this mapping. + */ + void addConstructorMapping(long id, RegisteredSensorConfig rsc); +} diff --git a/Agent/src/info/novatec/inspectit/agent/hooking/IHookInstrumenter.java b/Agent/src/info/novatec/inspectit/agent/hooking/IHookInstrumenter.java new file mode 100644 index 000000000..6ee45f78a --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/hooking/IHookInstrumenter.java @@ -0,0 +1,59 @@ +package info.novatec.inspectit.agent.hooking; + +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.hooking.impl.HookException; +import javassist.CtConstructor; +import javassist.CtMethod; + +/** + * The hook instrumenter interface defines methods to add method and constructor hooks. + * + * @author Patrice Bouillet + * @author Eduard Tudenhoefner + * + */ +public interface IHookInstrumenter { + + /** + * Adds the three hooks (one before, two after) to the passed {@link CtMethod}. Afterwards, the + * dispatcher stores the information so that the appropriate sensor types are called whenever + * the method is called. + * + * @param method + * The method which is instrumented. + * @param rsc + * The {@link RegisteredSensorConfig} class which holds the information which sensor + * types are called for this method. + * @throws HookException + * A {@link HookException} is thrown if something unexpected happens, like the + * instrumentation was not completed successfully. + */ + void addMethodHook(CtMethod method, RegisteredSensorConfig rsc) throws HookException; + + /** + * Adds two hooks to the passed {@link CtConstructor}. Afterwards, the dispatcher stores the + * information so that the appropriate sensor types are called whenever the constructor is + * called. + * + * @param constructor + * The constructor which is instrumented. + * @param rsc + * The {@link RegisteredSensorConfig} class which holds the information which sensor + * types are called for this method. + * @throws HookException + * A {@link HookException} is thrown if something unexpected happens, like the + * instrumentation was not completed successfully. + */ + void addConstructorHook(CtConstructor constructor, RegisteredSensorConfig rsc) throws HookException; + + /** + * Adds a class loader delegation check on this method and delegates class loading if necessary. + * + * @param ctMethod + * {@link CtMethod} to instrument. Note that the method will be replaced. + * @throws HookException + * A {@link HookException} is thrown if something unexpected happens, like the + * instrumentation was not completed successfully. + */ + void addClassLoaderDelegationHook(CtMethod ctMethod) throws HookException; +} \ No newline at end of file diff --git a/Agent/src/info/novatec/inspectit/agent/hooking/IMethodHook.java b/Agent/src/info/novatec/inspectit/agent/hooking/IMethodHook.java new file mode 100644 index 000000000..909db2cb4 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/hooking/IMethodHook.java @@ -0,0 +1,75 @@ +package info.novatec.inspectit.agent.hooking; + +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; + +/** + * Classes which add a hook into a method before and after it is called, have to implement this + * interface. + * + */ +public interface IMethodHook extends IHook { + + /** + * This method is executed before something else in the original method body will be executed. + * + * @param methodId + * The unique method id. + * @param sensorTypeId + * The unique sensor type id. + * @param object + * The class itself which contains the hook. + * @param parameters + * The parameters of the method call. + * @param rsc + * The {@link RegisteredSensorConfig} object which holds all the information of the + * executed method. + */ + void beforeBody(long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc); + + /** + * This method will be called before the original method will return. It is the first of two + * after body calls. It is important that a hook, implementing this method, just adds time or + * memory critical settings. Everything else, including computing or adding values to the value + * storage has to be added to the + * {@link #secondAfterBody(ICoreService, int, String, Object, Object[], Object, RegisteredSensorConfig)} + * implementation. + * + * @param methodId + * The unique method id. + * @param sensorTypeId + * The unique sensor type id. + * @param object + * The class itself which contains the hook. + * @param parameters + * The parameters of the method call. + * @param result + * The return value + * @param rsc + * The {@link RegisteredSensorConfig} object which holds all the information of the + * executed method. + */ + void firstAfterBody(long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc); + + /** + * This method will be called before the original method will return. It is the second of two + * after body calls. This method can be used to save or compute some values. + * + * @param coreService + * The reference to the core service which holds the data objects etc. + * @param methodId + * The unique method id. + * @param sensorTypeId + * The unique sensor type id. + * @param object + * The class itself which contains the hook. + * @param parameters + * The parameters of the method call. + * @param result + * The return value + * @param rsc + * The {@link RegisteredSensorConfig} object which holds all the information of the + * executed method. + */ + void secondAfterBody(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc); +} diff --git a/Agent/src/info/novatec/inspectit/agent/hooking/impl/HookDispatcher.java b/Agent/src/info/novatec/inspectit/agent/hooking/impl/HookDispatcher.java new file mode 100644 index 000000000..47c7e7846 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/hooking/impl/HookDispatcher.java @@ -0,0 +1,538 @@ +package info.novatec.inspectit.agent.hooking.impl; + +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.hooking.IConstructorHook; +import info.novatec.inspectit.agent.hooking.IHook; +import info.novatec.inspectit.agent.hooking.IHookDispatcher; +import info.novatec.inspectit.agent.hooking.IHookDispatcherMapper; +import info.novatec.inspectit.agent.hooking.IMethodHook; +import info.novatec.inspectit.agent.sensor.exception.IExceptionSensorHook; +import info.novatec.inspectit.agent.sensor.method.IMethodSensor; +import info.novatec.inspectit.spring.logger.Log; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * The hook dispatching service which is called by all the hooks throughout the instrumented target + * application. + * + * @author Patrice Bouillet + * @author Eduard Tudenhoefner + * + */ +@Component +public class HookDispatcher implements IHookDispatcherMapper, IHookDispatcher { + + /** + * The logger of this class. + */ + @Log + Logger log; + + /** + * The default core service. + */ + private final ICoreService coreService; + + /** + * Contains all method hooks. + */ + private Map methodHooks = new HashMap(); + + /** + * Contains all constructor hooks. + */ + private Map constructorHooks = new HashMap(); + + /** + * Stores the current Status of the invocation sequence tracer in a {@link ThreadLocal} object. + */ + private InvocationSequenceCount invocationSequenceCount = new InvocationSequenceCount(); + + /** + * A thread local holder object to save the current started invocation sequence. + */ + private ThreadLocal invocationSequenceHolder = new ThreadLocal(); + + /** + * If an execution of the dispatching is already in progress, we don't dispatch anything else + * for this thread. + */ + private ExecutionMarker executionMarker = new ExecutionMarker(); + + /** + * Default constructor which needs a reference to the core service. This is needed for the + * invocation sensor. + * + * @param coreService + * The core service. + */ + @Autowired + public HookDispatcher(ICoreService coreService) { + this.coreService = coreService; + } + + /** + * {@inheritDoc} + */ + public void addMethodMapping(long id, RegisteredSensorConfig rsc) { + methodHooks.put(Long.valueOf(id), rsc); + } + + /** + * {@inheritDoc} + */ + public void addConstructorMapping(long id, RegisteredSensorConfig rsc) { + constructorHooks.put(Long.valueOf(id), rsc); + } + + /** + * {@inheritDoc} + */ + public void dispatchMethodBeforeBody(long id, Object object, Object[] parameters) { + if (!executionMarker.isActive()) { + try { + executionMarker.active(); + + try { + RegisteredSensorConfig rsc = (RegisteredSensorConfig) methodHooks.get(Long.valueOf(id)); + + if (rsc.startsInvocationSequence()) { + // The sensor configuration contains an invocation sequence + // sensor. We have to set it on the thread local map for later + // access. Additionally, we need to save the count of the called + // invocation sensors, as another nested one could be started, + // too. + invocationSequenceCount.increment(); + + if (null == invocationSequenceHolder.get()) { + invocationSequenceHolder.set(((IMethodSensor) rsc.getInvocationSequenceSensorTypeConfig().getSensorType()).getHook()); + } + } else if (null != invocationSequenceHolder.get()) { + // We are executing the following sensor types in an invocation + // sequence context, thus we have to execute the before body + // method of the invocation sequence hook manually. + IMethodHook invocationHook = (IMethodHook) invocationSequenceHolder.get(); + + // The sensor type ID is not important here, thus we are passing + // a -1. It is already stored in the data object + invocationHook.beforeBody(id, -1, object, parameters, rsc); + } + + // Now iterate over all registered sensor types and execute them + for (Map.Entry entry : rsc.getReverseMethodHooks().entrySet()) { + IMethodHook methodHook = (IMethodHook) entry.getValue(); + methodHook.beforeBody(id, entry.getKey().longValue(), object, parameters, rsc); + } + } catch (Throwable throwable) { // NOPMD + log.error("An error happened in the Hook Dispatcher! (before body)", throwable); + } + } finally { + executionMarker.deactive(); + } + } + } + + /** + * {@inheritDoc} + */ + public void dispatchFirstMethodAfterBody(long id, Object object, Object[] parameters, Object returnValue) { + if (!executionMarker.isActive()) { + try { + executionMarker.active(); + + try { + RegisteredSensorConfig rsc = (RegisteredSensorConfig) methodHooks.get(Long.valueOf(id)); + for (Map.Entry entry : rsc.getMethodHooks().entrySet()) { + IMethodHook methodHook = (IMethodHook) entry.getValue(); + methodHook.firstAfterBody(id, entry.getKey().longValue(), object, parameters, returnValue, rsc); + } + } catch (Throwable throwable) { // NOPMD + log.error("An error happened in the Hook Dispatcher! (after body)", throwable); + } + } finally { + executionMarker.deactive(); + } + } + } + + /** + * {@inheritDoc} + */ + public void dispatchSecondMethodAfterBody(long id, Object object, Object[] parameters, Object returnValue) { + if (!executionMarker.isActive()) { + try { + executionMarker.active(); + + try { + RegisteredSensorConfig rsc = (RegisteredSensorConfig) methodHooks.get(Long.valueOf(id)); + + if (null != invocationSequenceHolder.get()) { + // Need to replace the core service with the one from the invocation + // sequence so that all data objects can be associated to that invocation + // record. + ICoreService invocCoreService = (ICoreService) invocationSequenceHolder.get(); + + for (Map.Entry entry : rsc.getMethodHooks().entrySet()) { + IMethodHook methodHook = (IMethodHook) entry.getValue(); + // the invocation sequence sensor needs the original core service! + if (invocCoreService == methodHook) { // NOPMD + methodHook.secondAfterBody(coreService, id, entry.getKey().longValue(), object, parameters, returnValue, rsc); + } else { + methodHook.secondAfterBody(invocCoreService, id, entry.getKey().longValue(), object, parameters, returnValue, rsc); + } + } + } else { + for (Map.Entry entry : rsc.getMethodHooks().entrySet()) { + IMethodHook methodHook = (IMethodHook) entry.getValue(); + methodHook.secondAfterBody(coreService, id, entry.getKey().longValue(), object, parameters, returnValue, rsc); + } + } + + if (rsc.startsInvocationSequence()) { + invocationSequenceCount.decrement(); + + if (0 == invocationSequenceCount.getCount()) { + invocationSequenceHolder.set(null); + } + } else if (null != invocationSequenceHolder.get()) { + // We have to execute the after body method of the invocation sequence hook + // manually. + IMethodHook invocationHook = (IMethodHook) invocationSequenceHolder.get(); + + // The sensor type ID is not important here, thus we are passing a -1. It is + // already stored in the data object + invocationHook.secondAfterBody(coreService, id, -1, object, parameters, returnValue, rsc); + } + } catch (Throwable throwable) { // NOPMD + log.error("An error happened in the Hook Dispatcher! (second after body)", throwable); + } + } finally { + executionMarker.deactive(); + } + } + } + + /** + * {@inheritDoc} + */ + public void dispatchOnThrowInBody(long id, Object object, Object[] parameters, Object exceptionObject) { + if (!executionMarker.isActive()) { + try { + executionMarker.active(); + + // rsc contains the settings for the actual method where the exception was thrown. + RegisteredSensorConfig rsc = (RegisteredSensorConfig) methodHooks.get(Long.valueOf(id)); + long sensorTypeId = rsc.getExceptionSensorTypeConfig().getId(); + + ICoreService invocCoreService = null; + if (null != invocationSequenceHolder.get()) { + // Need to replace the core service with the one from the invocation sequence so + // that all data objects can be associated to that invocation record. + invocCoreService = (ICoreService) invocationSequenceHolder.get(); + } + + IExceptionSensorHook exceptionHook = (IExceptionSensorHook) ((IMethodSensor) rsc.getExceptionSensorTypeConfig().getSensorType()).getHook(); + + if (null != invocCoreService) { + exceptionHook.dispatchOnThrowInBody(invocCoreService, id, sensorTypeId, object, exceptionObject, parameters, rsc); + } else { + exceptionHook.dispatchOnThrowInBody(coreService, id, sensorTypeId, object, exceptionObject, parameters, rsc); + } + } finally { + executionMarker.deactive(); + } + } + } + + /** + * {@inheritDoc} + */ + public void dispatchBeforeCatch(long id, Object exceptionObject) { + if (!executionMarker.isActive()) { + try { + executionMarker.active(); + + // rsc contains the settings of the actual method where the exception is catched. + RegisteredSensorConfig rsc = (RegisteredSensorConfig) methodHooks.get(Long.valueOf(id)); + long sensorTypeId = rsc.getExceptionSensorTypeConfig().getId(); + + ICoreService invocCoreService = null; + if (null != invocationSequenceHolder.get()) { + // Need to replace the core service with the one from the invocation sequence so + // that all data objects can be associated to that invocation record. + invocCoreService = (ICoreService) invocationSequenceHolder.get(); + } + + IExceptionSensorHook exceptionHook = (IExceptionSensorHook) ((IMethodSensor) rsc.getExceptionSensorTypeConfig().getSensorType()).getHook(); + + if (null != invocCoreService) { + exceptionHook.dispatchBeforeCatchBody(invocCoreService, id, sensorTypeId, exceptionObject, rsc); + } else { + exceptionHook.dispatchBeforeCatchBody(coreService, id, sensorTypeId, exceptionObject, rsc); + } + } finally { + executionMarker.deactive(); + } + } + } + + /** + * {@inheritDoc} + */ + public void dispatchConstructorOnThrowInBody(long id, Object object, Object[] parameters, Object exceptionObject) { + if (!executionMarker.isActive()) { + try { + executionMarker.active(); + + // rsc contains the settings for the actual constructor where the exception was + // thrown. + RegisteredSensorConfig rsc = (RegisteredSensorConfig) constructorHooks.get(Long.valueOf(id)); + long sensorTypeId = rsc.getExceptionSensorTypeConfig().getId(); + + ICoreService invocCoreService = null; + if (null != invocationSequenceHolder.get()) { + // Need to replace the core service with the one from the invocation sequence so + // that all data objects can be associated to that invocation record. + invocCoreService = (ICoreService) invocationSequenceHolder.get(); + } + + IExceptionSensorHook exceptionHook = (IExceptionSensorHook) ((IMethodSensor) rsc.getExceptionSensorTypeConfig().getSensorType()).getHook(); + + if (null != invocCoreService) { + exceptionHook.dispatchOnThrowInBody(invocCoreService, id, sensorTypeId, object, exceptionObject, parameters, rsc); + } else { + exceptionHook.dispatchOnThrowInBody(coreService, id, sensorTypeId, object, exceptionObject, parameters, rsc); + } + } finally { + executionMarker.deactive(); + } + } + } + + /** + * {@inheritDoc} + */ + public void dispatchConstructorBeforeCatch(long id, Object exceptionObject) { + if (!executionMarker.isActive()) { + try { + executionMarker.active(); + + // rsc contains the settings of the actual constructor where the exception is + // catched. + RegisteredSensorConfig rsc = (RegisteredSensorConfig) constructorHooks.get(Long.valueOf(id)); + long sensorTypeId = rsc.getExceptionSensorTypeConfig().getId(); + + ICoreService invocCoreService = null; + if (null != invocationSequenceHolder.get()) { + // Need to replace the core service with the one from the invocation sequence so + // that all data objects can be associated to that invocation record. + invocCoreService = (ICoreService) invocationSequenceHolder.get(); + } + + IExceptionSensorHook exceptionHook = (IExceptionSensorHook) ((IMethodSensor) rsc.getExceptionSensorTypeConfig().getSensorType()).getHook(); + + if (null != invocCoreService) { + exceptionHook.dispatchBeforeCatchBody(invocCoreService, id, sensorTypeId, exceptionObject, rsc); + } else { + exceptionHook.dispatchBeforeCatchBody(coreService, id, sensorTypeId, exceptionObject, rsc); + } + } finally { + executionMarker.deactive(); + } + } + } + + /** + * {@inheritDoc} + */ + public void dispatchConstructorBeforeBody(long id, Object[] parameters) { + if (!executionMarker.isActive()) { + try { + executionMarker.active(); + + try { + RegisteredSensorConfig rsc = (RegisteredSensorConfig) constructorHooks.get(Long.valueOf(id)); + + if (rsc.startsInvocationSequence()) { + // The sensor configuration contains an invocation sequence sensor. We have + // to set it on the thread local map for later access. Additionally, we need + // to save the count of the called invocation sensors, as another nested one + // could be started, too. + invocationSequenceCount.increment(); + if (null == invocationSequenceHolder.get()) { + invocationSequenceHolder.set(((IMethodSensor) rsc.getInvocationSequenceSensorTypeConfig().getSensorType()).getHook()); + } + } else if (null != invocationSequenceHolder.get()) { + // We are executing the following sensor types in an invocation sequence + // context, thus we have to execute the before body method of the invocation + // sequence hook manually. + IConstructorHook invocationHook = (IConstructorHook) invocationSequenceHolder.get(); + + // The sensor type ID is not important here, thus we are passing a -1. It is + // already stored in the data object + invocationHook.beforeConstructor(id, -1, parameters, rsc); + } + + // Now iterate over all registered sensor types and execute them + for (Map.Entry entry : rsc.getReverseMethodHooks().entrySet()) { + IConstructorHook constructorHook = (IConstructorHook) entry.getValue(); + constructorHook.beforeConstructor(id, entry.getKey().longValue(), parameters, rsc); + } + } catch (Throwable throwable) { // NOPMD + log.error("An error happened in the Hook Dispatcher! (before constructor)", throwable); + } + } finally { + executionMarker.deactive(); + } + } + } + + /** + * {@inheritDoc} + */ + public void dispatchConstructorAfterBody(long id, Object object, Object[] parameters) { + if (!executionMarker.isActive()) { + try { + executionMarker.active(); + + try { + RegisteredSensorConfig rsc = (RegisteredSensorConfig) constructorHooks.get(Long.valueOf(id)); + + if (null != invocationSequenceHolder.get()) { + // Need to replace the core service with the one from the invocation + // sequence so that all data objects can be associated to that invocation + // record. + ICoreService invocCoreService = (ICoreService) invocationSequenceHolder.get(); + + for (Map.Entry entry : rsc.getMethodHooks().entrySet()) { + IConstructorHook constructorHook = (IConstructorHook) entry.getValue(); + // the invocation sequence sensor and the exception sensor need the + // original core service! + if (invocCoreService == constructorHook) { // NOPMD + constructorHook.afterConstructor(coreService, id, entry.getKey().longValue(), object, parameters, rsc); + } else { + constructorHook.afterConstructor(invocCoreService, id, entry.getKey().longValue(), object, parameters, rsc); + } + } + } else { + for (Map.Entry entry : rsc.getMethodHooks().entrySet()) { + IConstructorHook constructorHook = (IConstructorHook) entry.getValue(); + constructorHook.afterConstructor(coreService, id, entry.getKey().longValue(), object, parameters, rsc); + } + } + + if (rsc.startsInvocationSequence()) { + invocationSequenceCount.decrement(); + + if (0 == invocationSequenceCount.getCount()) { + invocationSequenceHolder.set(null); + } + } else if (null != invocationSequenceHolder.get()) { + // We have to execute the after body method of the invocation + // sequence hook manually. + IConstructorHook invocationHook = (IConstructorHook) invocationSequenceHolder.get(); + + // The sensor type ID is not important here, thus we are passing + // a -1. It is already stored in the data object + invocationHook.afterConstructor(coreService, id, -1, object, parameters, rsc); + } + } catch (Throwable throwable) { // NOPMD + log.error("An error happened in the Hook Dispatcher! (after constructor)", throwable); + } + } finally { + executionMarker.deactive(); + } + } + } + + /** + * Private inner class used to track the count of the started invocation sequences in one + * thread. Thus it extends {@link ThreadLocal} to provide a unique number for every Thread. + * + * @author Patrice Bouillet + * + */ + private static class InvocationSequenceCount extends ThreadLocal { + + /** + * {@inheritDoc} + */ + protected Long initialValue() { + return Long.valueOf(0); + } + + /** + * Increments the stored value. + */ + public void increment() { + super.set(Long.valueOf(super.get().longValue() + 1)); + } + + /** + * Decrements the stored value. + */ + public void decrement() { + super.set(Long.valueOf(super.get().longValue() - 1)); + } + + /** + * Returns the current count. + * + * @return The count. + */ + public long getCount() { + return super.get().longValue(); + } + + } + + /** + * ThreadLocal execution marker which is used to mark executions as already in progress to not + * dispatch over and over again. + * + * @author Patrice Bouillet + * + */ + private static class ExecutionMarker extends ThreadLocal { + + /** + * {@inheritDoc} + */ + protected Boolean initialValue() { + return Boolean.FALSE; + } + + /** + * Execution is active. + */ + public void active() { + super.set(Boolean.TRUE); + } + + /** + * Execution is deactive. + */ + public void deactive() { + super.set(Boolean.FALSE); + } + + /** + * Defines if our own execution is active, and thus we have to skip the whole processing + * (because it could happen, that we'll never end then). + * + * @return if own execution is active. + */ + public boolean isActive() { + return super.get().booleanValue(); + } + + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/hooking/impl/HookException.java b/Agent/src/info/novatec/inspectit/agent/hooking/impl/HookException.java new file mode 100644 index 000000000..653036bc3 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/hooking/impl/HookException.java @@ -0,0 +1,36 @@ +package info.novatec.inspectit.agent.hooking.impl; + +/** + * This exception is thrown when a something bad happened at hooking a class/method. + * + * @author Patrice Bouillet + */ +public class HookException extends Exception { + + /** + * The serial version UID. + */ + private static final long serialVersionUID = 1043321043946792608L; + + /** + * Default constructor which takes one argument used for a message of the exception. + * + * @param msg + * The message. + */ + public HookException(String msg) { + super(msg); + } + + /** + * Additional constructor which can store the origin exception. + * + * @param msg + * The message. + * @param throwable + * The origin exception. + */ + public HookException(String msg, Throwable throwable) { + super(msg, throwable); + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/hooking/impl/HookInstrumenter.java b/Agent/src/info/novatec/inspectit/agent/hooking/impl/HookInstrumenter.java new file mode 100644 index 000000000..694be6664 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/hooking/impl/HookInstrumenter.java @@ -0,0 +1,364 @@ +package info.novatec.inspectit.agent.hooking.impl; + +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.config.impl.MethodSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.hooking.IHookDispatcherMapper; +import info.novatec.inspectit.agent.hooking.IHookInstrumenter; +import info.novatec.inspectit.spring.logger.Log; +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.Modifier; +import javassist.NotFoundException; +import javassist.expr.ExprEditor; +import javassist.expr.Handler; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * The byte code instrumenter class. Used to instrument the additional instructions into the target + * byte code. + * + * @author Patrice Bouillet + * @author Eduard Tudenhoefner + * + */ +@Component +public class HookInstrumenter implements IHookInstrumenter { + + /** + * The logger of the class. + */ + @Log + Logger log; + + /** + * The hook dispatcher. This string shouldn't be touched. For changing the dispatcher, alter the + * hook dispatcher instance in the Agent class. + */ + private static String hookDispatcherTarget = "info.novatec.inspectit.agent.Agent#agent.getHookDispatcher()"; + + /** + * The agent target as string. + */ + private static String agentTarget = "info.novatec.inspectit.agent.Agent#agent"; + + /** + * The hook dispatching service. + */ + private final IHookDispatcherMapper hookDispatcher; + + /** + * The ID manager used to register the methods and the mapping between the method sensor type id + * and the method id. + */ + private final IIdManager idManager; + + /** + * The expression editor to modify a method body. + */ + private MethodExprEditor methodExprEditor = new MethodExprEditor(); + + /** + * The expression editor to modify a constructor body. + */ + private ConstructorExprEditor constructorExprEditor = new ConstructorExprEditor(); + + /** + * The implementation of the configuration storage where all definitions of the user are stored. + */ + private IConfigurationStorage configurationStorage; + + /** + * The default and only constructor for this class. + * + * @param hookDispatcher + * The hook dispatcher which is used in the Agent. + * @param idManager + * The ID manager. + * @param configurationStorage + * The configuration storage where all definitions of the user are stored. + */ + @Autowired + public HookInstrumenter(IHookDispatcherMapper hookDispatcher, IIdManager idManager, IConfigurationStorage configurationStorage) { + this.hookDispatcher = hookDispatcher; + this.idManager = idManager; + this.configurationStorage = configurationStorage; + } + + /** + * {@inheritDoc} + */ + public void addMethodHook(CtMethod method, RegisteredSensorConfig rsc) throws HookException { + if (log.isDebugEnabled()) { + log.debug("Match found! Class: " + rsc.getTargetClassName() + " Method: " + rsc.getTargetMethodName() + " Parameter: " + rsc.getParameterTypes() + " id: " + rsc.getId()); + } + + if (method.getDeclaringClass().isFrozen()) { + // defrost before we are adding any instructions + method.getDeclaringClass().defrost(); + } + + final long methodId = idManager.registerMethod(rsc); + + for (MethodSensorTypeConfig config : rsc.getSensorTypeConfigs()) { + long sensorTypeId = config.getId(); + idManager.addSensorTypeToMethod(sensorTypeId, methodId); + } + + try { + boolean exceptionSensorActivated = configurationStorage.isExceptionSensorActivated(); + boolean exceptionSensorEnhanced = configurationStorage.isEnhancedExceptionSensorActivated(); + // instrument as finally if exception sensor is deactivated or activated in simple mode + boolean asFinally = !(exceptionSensorActivated && exceptionSensorEnhanced); + + if (Modifier.isStatic(method.getModifiers())) { + // static method + method.insertBefore(hookDispatcherTarget + ".dispatchMethodBeforeBody(" + methodId + "l, null, $args);"); + method.insertAfter(hookDispatcherTarget + ".dispatchFirstMethodAfterBody(" + methodId + "l, null, $args, ($w)$_);", asFinally); + method.insertAfter(hookDispatcherTarget + ".dispatchSecondMethodAfterBody(" + methodId + "l, null, $args, ($w)$_);", asFinally); + + if (!asFinally) { + // the exception sensor is activated, so instrument the + // static method with an addCatch + instrumentMethodWithTryCatch(method, methodId, true); + } + } else { + // normal method + method.insertBefore(hookDispatcherTarget + ".dispatchMethodBeforeBody(" + methodId + "l, $0, $args);"); + method.insertAfter(hookDispatcherTarget + ".dispatchFirstMethodAfterBody(" + methodId + "l, $0, $args, ($w)$_);", asFinally); + method.insertAfter(hookDispatcherTarget + ".dispatchSecondMethodAfterBody(" + methodId + "l, $0, $args, ($w)$_);", asFinally); + + if (!asFinally) { + // the exception sensor is activated, so instrument the + // normal method with an addCatch + instrumentMethodWithTryCatch(method, methodId, false); + } + } + + // Add the information to the dispatching service + hookDispatcher.addMethodMapping(methodId, rsc); + } catch (CannotCompileException cannotCompileException) { + throw new HookException("Could not insert the bytecode into the method/class", cannotCompileException); + } catch (NotFoundException notFoundException) { + // for the addCatch method needed + throw new HookException("Could not insert the bytecode into the method/class", notFoundException); + } + } + + /** + * {@inheritDoc} + */ + public void addConstructorHook(CtConstructor constructor, RegisteredSensorConfig rsc) throws HookException { + if (log.isDebugEnabled()) { + log.debug("Constructor match found! Class: " + rsc.getTargetClassName() + " Parameter: " + rsc.getParameterTypes() + " id: " + rsc.getId()); + } + + if (constructor.getDeclaringClass().isFrozen()) { + // defrost before we are adding any instructions + constructor.getDeclaringClass().defrost(); + } + + long constructorId = idManager.registerMethod(rsc); + + for (MethodSensorTypeConfig config : rsc.getSensorTypeConfigs()) { + long sensorTypeId = config.getId(); + idManager.addSensorTypeToMethod(sensorTypeId, constructorId); + } + + try { + boolean exceptionSensorActivated = configurationStorage.isExceptionSensorActivated(); + boolean exceptionSensorEnhanced = configurationStorage.isEnhancedExceptionSensorActivated(); + // instrument as finally if exception sensor is deactivated or activated in simple mode + boolean asFinally = !(exceptionSensorActivated && exceptionSensorEnhanced); + + constructor.insertBefore(hookDispatcherTarget + ".dispatchConstructorBeforeBody(" + constructorId + "l, $args);"); + constructor.insertAfter(hookDispatcherTarget + ".dispatchConstructorAfterBody(" + constructorId + "l, $0, $args);", asFinally); + + if (!asFinally) { + instrumentConstructorWithTryCatch(constructor, constructorId); + } + + // Add the information to the dispatching service + hookDispatcher.addConstructorMapping(constructorId, rsc); + } catch (CannotCompileException cannotCompileException) { + throw new HookException("Could not insert the bytecode into the constructor/class", cannotCompileException); + } catch (NotFoundException notFoundException) { + throw new HookException("Could not insert the bytecode into the constructor/class", notFoundException); + } + } + + /** + * {@inheritDoc} + */ + public void addClassLoaderDelegationHook(CtMethod ctMethod) throws HookException { + if (log.isDebugEnabled()) { + log.debug("Class loader delegation match found! Method signature: " + ctMethod.getSignature()); + } + + if (ctMethod.getDeclaringClass().isFrozen()) { + // defrost before we are adding any instructions + ctMethod.getDeclaringClass().defrost(); + } + + try { + ctMethod.insertBefore("Class c = " + agentTarget + ".loadClass($args); if (null != c) { return c; }"); + } catch (CannotCompileException cannotCompileException) { + throw new HookException("Could not insert the bytecode into the method/class for class loader delegation", cannotCompileException); + } + } + + /** + * The passed {@link CtMethod} is instrumented with an internal try-catch block to + * get an event when an exception is thrown in a method body. + * + * @see info.novatec.inspectit.javassist.CtMethod#addCatch(String, CtClass) + * + * @param method + * The {@link CtMethod} where additional instructions are added. + * @param methodId + * The method id of the passed method. + * @param isStatic + * Defines whether the current method is a static method. + * @throws CannotCompileException + * When the additional instructions could not be compiled by Javassist. + * @throws NotFoundException + * When {@link Throwable} cannot be found in the default {@link ClassPool}. + */ + private void instrumentMethodWithTryCatch(CtMethod method, long methodId, boolean isStatic) throws CannotCompileException, NotFoundException { + if (configurationStorage.isExceptionSensorActivated()) { + // we instrument the method with an expression editor to get events + // when a handler of an exception is found. + methodExprEditor.setId(methodId); + method.instrument(methodExprEditor); + } + + CtClass type = ClassPool.getDefault().get("java.lang.Throwable"); + + if (!isStatic) { + // normal method + method.addCatch(hookDispatcherTarget + ".dispatchOnThrowInBody(" + methodId + "l, $0, $args, $e);" + hookDispatcherTarget + ".dispatchFirstMethodAfterBody(" + methodId + + "l, $0, $args, null);" + hookDispatcherTarget + ".dispatchSecondMethodAfterBody(" + methodId + "l, $0, $args, null);" + "throw $e; ", type); + } else { + // static method + method.addCatch(hookDispatcherTarget + ".dispatchOnThrowInBody(" + methodId + "l, null, $args, $e);" + hookDispatcherTarget + ".dispatchFirstMethodAfterBody(" + methodId + + "l, null, $args, null);" + hookDispatcherTarget + ".dispatchSecondMethodAfterBody(" + methodId + "l, null, $args, null);" + "throw $e; ", type); + } + } + + /** + * The passed {@link CtConstructor} is instrumented with an internal try-catch + * block to get an event when an exception is thrown in a constructor body. + * + * @see info.novatec.inspectit.javassist.CtConstructor#addCatch(String, CtClass) + * + * @param constructor + * The {@link CtConstructor} where additional instructions are added. + * @param constructorId + * The constructor id of the passed constructor. + * @throws CannotCompileException + * When the additional instructions could not be compiled by Javassist. + * @throws NotFoundException + * When {@link Throwable} cannot be found in the default {@link ClassPool}. + */ + private void instrumentConstructorWithTryCatch(CtConstructor constructor, long constructorId) throws CannotCompileException, NotFoundException { + if (configurationStorage.isExceptionSensorActivated()) { + // we instrument the constructor with an expression editor to get + // events when a handler of an exception is found. + constructorExprEditor.setId(constructorId); + constructor.instrument(constructorExprEditor); + } + + CtClass type = ClassPool.getDefault().get("java.lang.Throwable"); + constructor.addCatch(hookDispatcherTarget + ".dispatchConstructorOnThrowInBody(" + constructorId + "l, $0, $args, $e);" + hookDispatcherTarget + ".dispatchConstructorAfterBody(" + + constructorId + "l, $0, $args);" + "throw $e; ", type); + } + + /** + * If instrument() is called in CtMethod, the method body is scanned + * from the beginning to the end. Whenever an expression, such as a Handler of an Exception is + * found, edit() is called in ExprEditor. edit() can + * inspect and modify the given expression. The modification is reflected on the original method + * body. If edit() does nothing, the original method body is not changed. + * + * @see info.novatec.inspectit.javassist.expr.ExprEditor + * + * @author Eduard Tudenhoefner + * + */ + public static class MethodExprEditor extends ExprEditor { + + /** + * The id of the method which is instrumented with the ExpressionEditor. + */ + private long id = 0; + + /** + * Sets the method id. + * + * @param id + * The id of the method to instrument. + */ + public void setId(long id) { + this.id = id; + } + + /** + * {@inheritDoc} + */ + public void edit(Handler handler) throws CannotCompileException { + if (!handler.isFinally()) { + // $1 is the exception object + handler.insertBefore(hookDispatcherTarget + ".dispatchBeforeCatch(" + id + "l, $1);"); + } + } + } + + /** + * If instrument() is called in CtConstructor, the constructor body is + * scanned from the beginning to the end. Whenever an expression, such as a Handler of an + * Exception is found, edit() is called in ExprEditor. + * edit() can inspect and modify the given expression. The modification is + * reflected on the original constructor body. If edit() does nothing, the original + * constructor body is not changed. + * + * @see info.novatec.inspectit.javassist.expr.ExprEditor + * + * @author Eduard Tudenhoefner + * + */ + public static class ConstructorExprEditor extends ExprEditor { + + /** + * The id of the constructor which is instrumented with the ExpressionEditor. + */ + private long id = 0; + + /** + * Sets the constructor id. + * + * @param id + * The id of the constructor to instrument. + */ + public void setId(long id) { + this.id = id; + } + + /** + * {@inheritDoc} + */ + public void edit(Handler handler) throws CannotCompileException { + if (!handler.isFinally()) { + // $1 is the exception object + handler.insertBefore(hookDispatcherTarget + ".dispatchConstructorBeforeCatch(" + id + "l, $1);"); + } + } + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/javaagent/JavaAgent.java b/Agent/src/info/novatec/inspectit/agent/javaagent/JavaAgent.java new file mode 100644 index 000000000..114e854dc --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/javaagent/JavaAgent.java @@ -0,0 +1,515 @@ +package info.novatec.inspectit.agent.javaagent; + +import info.novatec.inspectit.agent.Agent; +import info.novatec.inspectit.agent.IAgent; +import info.novatec.inspectit.agent.hooking.IHookDispatcher; + +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.instrument.ClassDefinition; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.lang.instrument.Instrumentation; +import java.lang.instrument.UnmodifiableClassException; +import java.lang.management.ManagementFactory; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.AllPermission; +import java.security.CodeSource; +import java.security.PermissionCollection; +import java.security.ProtectionDomain; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * The JavaAgent is used since Java 5.0 to instrument classes before they are actually loaded by the + * VM. + * + * This method is used by specifying the -javaagent attribute on the command line. Example: + * -javaagent:inspectit-agent.jar + * + * @author Patrice Bouillet + */ +public class JavaAgent implements ClassFileTransformer { + + /** + * The logger of this class. + */ + private static final Logger LOGGER = Logger.getLogger(JavaAgent.class.getName()); + + /** + * In case that multiple classes are loaded at the same time, which happens in some cases, even + * though the JVM specification prohibits that (the case at hand was starting ant). + */ + private static volatile boolean operationInProgress = false; + + /** + * The reference to the instrumentation class. + */ + private static Instrumentation instrumentation; + + /** + * Defines if we can instrument core classes. + */ + private static boolean instrumentCoreClasses = false; + + /** + * Defines the class of our current real agent to use. + */ + private static final String INSPECTIT_AGENT = "info.novatec.inspectit.agent.SpringAgent"; + + /** + * Defines the self first classes which should be loaded by this class loader instead of + * delegating the loading to the parent. + */ + private static Set selfFirstClasses = new HashSet(); + + /** + * The premain method will be executed before anything else. + * + * @param agentArgs + * Some arguments. + * @param inst + * The instrumentation instance is used to add a transformer which will do the actual + * instrumentation. + */ + public static void premain(String agentArgs, Instrumentation inst) { + instrumentation = inst; + + LOGGER.info("inspectIT Agent: Starting initialization..."); + checkForCorrectSetup(); + + // Starting up the real agent + try { + // now we load the PicoAgent via our own classloader + @SuppressWarnings("resource") + InspectItClassLoader classLoader = new InspectItClassLoader(new URL[0], JavaAgent.class.getClassLoader()); + Class agentClazz = classLoader.loadClass(INSPECTIT_AGENT); + Constructor constructor = agentClazz.getConstructor(String.class); + Object realAgent = constructor.newInstance(getInspectItAgentJarFileLocation()); + + // we can reference the Agent now here because it should have been added to the + // bootclasspath and thus available from anywhere in the application + Agent.agent = (IAgent) realAgent; + + // we need to preload some classes due to the minimal possibility of classcircularity + // errors etc. + preloadClasses(); + + LOGGER.info("inspectIT Agent: Initialization complete..."); + + // now we are analysing the already loaded classes by the jvm to instrument those + // classes, too + analyzeAlreadyLoadedClasses(); + inst.addTransformer(new JavaAgent()); + } catch (Exception e) { + LOGGER.severe("Something unexpected happened while trying to initialize the Agent, aborting!"); + e.printStackTrace(); // NOPMD + } + } + + /** + * {@inheritDoc} + */ + public byte[] transform(ClassLoader classLoader, String className, Class clazz, ProtectionDomain pd, byte[] data) throws IllegalClassFormatException { + try { + if (null != classLoader && InspectItClassLoader.class.getCanonicalName().equals(classLoader.getClass().getCanonicalName())) { + // return if the classloader to load the class is our own, we don't want to + // instrument these classes. + return data; + } + // early return if some conditions fail + if (null == data || data.length == 0 || null == className || "".equals(className)) { + // - no data = we cannot construct the class and analyze it + // - no class name = we don't know how the name of the class is and so the whole + // analysis will fail + return data; + } + + // skip analyzing if we cannot instrument core classes. + if (!instrumentCoreClasses & null == classLoader) { + return data; + } + + // now the real inspectit agent will handle this class + if (!operationInProgress) { + operationInProgress = true; + String modifiedClassName = className.replaceAll("/", "."); + byte[] instrumentedData = Agent.agent.inspectByteCode(data, modifiedClassName, classLoader); + operationInProgress = false; + return instrumentedData; + } + // LOGGER.severe("Parallel loading of classes: Skipping class "+className); + return data; + } catch (Throwable ex) { // NOPMD + LOGGER.severe("Error occurred while dealing with class: " + className + " " + ex.getMessage()); + ex.printStackTrace(); // NOPMD + return null; + } + } + + /** + * Checks for the correct setup of the jvm parameters or tries to append the inspectit agent to + * the bootstrap class loader search automatically (Java 6+ required). + */ + private static void checkForCorrectSetup() { + try { + // we can utilize the mechanism to add the inspectit-agent to the bootstrap classloader + // through the instrumentation api. + Method append = instrumentation.getClass().getDeclaredMethod("appendToBootstrapClassLoaderSearch", JarFile.class); + append.setAccessible(true); + append.invoke(instrumentation, new JarFile(getInspectItAgentJarFileLocation())); + + instrumentCoreClasses = true; + } catch (NoSuchMethodException e) { + LOGGER.info("inspectIT Agent: Advanced instrumentation capabilities not detected..."); + } catch (SecurityException e) { + LOGGER.info("inspectIT Agent: Advanced instrumentation capabilities not detected due to security constraints..."); + } catch (Exception e) { + LOGGER.severe("Something unexpected happened while trying to get advanced instrumentation capabilities!"); + e.printStackTrace(); // NOPMD + } + + if (!instrumentCoreClasses) { + // 2. try + // find out if the bootclasspath option is set + List inputArgs = ManagementFactory.getRuntimeMXBean().getInputArguments(); + for (String arg : inputArgs) { + if (arg.contains("Xbootclasspath") && arg.contains("inspectit-agent.jar")) { + instrumentCoreClasses = true; + LOGGER.info("inspectIT Agent: Xbootclasspath setting found, activating core class instrumentation..."); + break; + } + } + } + } + + /** + * Analyzes all the classes which are already loaded by the jvm. This only works if the + * -Xbootclasspath option is being set in addition as we are instrumenting core classes which + * are directly connected to the bootstrap classloader. + */ + private static void analyzeAlreadyLoadedClasses() { + try { + if (instrumentation.isRedefineClassesSupported()) { + if (instrumentCoreClasses) { + for (Class loadedClass : instrumentation.getAllLoadedClasses()) { + String clazzName = loadedClass.getCanonicalName(); + if (null != clazzName && !selfFirstClasses.contains(clazzName)) { + if (null == loadedClass.getClassLoader() || !InspectItClassLoader.class.getCanonicalName().equals(loadedClass.getClassLoader().getClass().getCanonicalName())) { + try { + clazzName = getClassNameForJavassist(loadedClass); + byte[] modified = Agent.agent.inspectByteCode(null, clazzName, loadedClass.getClassLoader()); + if (null != modified) { + ClassDefinition classDefinition = new ClassDefinition(loadedClass, modified); + instrumentation.redefineClasses(new ClassDefinition[] { classDefinition }); + } + } catch (ClassNotFoundException e) { + LOGGER.severe(e.getMessage()); + } catch (UnmodifiableClassException e) { + LOGGER.severe(e.getMessage()); + } + } + } + } + LOGGER.info("inspectIT Agent: Instrumentation of core classes finished..."); + } else { + LOGGER.info("inspectIT Agent: Core classes cannot be instrumented, please add -Xbootclasspath/a: to the JVM parameters!"); + } + } else { + LOGGER.info("Redefinition of Classes is not supported in this JVM!"); + } + } catch (Throwable t) { // NOPMD + t.printStackTrace(); // NOPMD + LOGGER.severe("The process of class redefinitions produced an error: " + t.getMessage()); + LOGGER.severe("If you are running on an IBM JVM, please ignore this error as the JVM does not support this feature!"); + LOGGER.throwing(JavaAgent.class.getCanonicalName(), "analyzeAlreadyLoadedClasses", t); + } + } + + /** + * Preload some classes to prevent errors in the running application. + */ + private static void preloadClasses() { + LOGGER.info("Preloading classes ..."); + + StringIndexOutOfBoundsException.class.getClass(); + + LOGGER.info("Preloading classes complete..."); + } + + /** + * See ClassPool#get(String) why it is needed to replace the '.' with '$' for inner class. + * + * @param clazz + * The class to get the name from. + * @return the name to be passed to javassist. + */ + private static String getClassNameForJavassist(Class clazz) { + String clazzName = clazz.getCanonicalName(); + while (null != clazz.getEnclosingClass()) { + clazz = clazz.getEnclosingClass(); + } + + if (!clazzName.equals(clazz.getCanonicalName())) { + String enclosingClasses = clazzName.substring(clazz.getCanonicalName().length()); + enclosingClasses = enclosingClasses.replaceAll("\\.", "\\$"); + clazzName = clazz.getCanonicalName() + enclosingClasses; + } + + return clazzName; + } + + /** + * Returns the path to the inspectit-agent.jar file. + * + * @return the path to the jar file. + */ + public static String getInspectItAgentJarFileLocation() { + CodeSource cs = JavaAgent.class.getProtectionDomain().getCodeSource(); + if (null != cs) { + // no bootstrap definition for the inspectit agent is in place, thus we can use this + // mechanism + return cs.getLocation().getFile(); + } else { + List inputArgs = ManagementFactory.getRuntimeMXBean().getInputArguments(); + for (String arg : inputArgs) { + if (arg.contains("javaagent") && arg.contains("inspectit-agent.jar")) { + // -javaagent:c:/.../inspectit-agent.jar + // -javaagent:/home/.../inspectit-agent.jar + Pattern pattern = Pattern.compile("-javaagent:(.*\\.jar)"); + Matcher matcher = pattern.matcher(arg); + boolean matches = matcher.matches(); + if (matches) { + String path = matcher.group(1); + // for multiple javaagent definitions, this will fail, but we won't include + // this right now. + return path; + } else { + break; + } + } + } + } + return null; + } + + /** + * Self first class loader handling the boundaries of our needed dependency classes and + * inspectit classes so we don't mess up with the target. + * + * @author Patrice Bouillet + * + */ + public static class InspectItClassLoader extends URLClassLoader { + + /** + * We need to ignore some of the self first classes so that they are accessible from this + * class (different class loader) and from the SUD. + */ + private Set ignoreClasses = new HashSet(); + + /** + * Default constructor initialized with the urls of the dependency jars etc. and the parent + * classloader. + * + * @param urls + * the urls to search for the classes for. + * @param parent + * the parent class loader. + */ + public InspectItClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + + try { + String agentFile = getInspectItAgentJarFileLocation(); + if (isJar(agentFile)) { + addJarResource(new File(agentFile)); + } else { + LOGGER.severe("There was a problem in retrieving the root jar name!"); + throw new RuntimeException("There was a problem in retrieving the root jar name!"); + } + } catch (IOException e) { + LOGGER.severe("There was a problem in extracting needed libs for the inspectIT agent: " + e.getMessage()); + LOGGER.throwing(InspectItClassLoader.class.getCanonicalName(), "InspectItClassLoader", e); + } + + // ignore IAgent because this is the interface for the SUD to access the real agent + ignoreClasses.add(IAgent.class.getCanonicalName()); + ignoreClasses.add(Agent.class.getCanonicalName()); + + // ignore hook dispatcher because it is defined in the IAgent interface and thus must be + // available in the standard classloader. + ignoreClasses.add(IHookDispatcher.class.getCanonicalName()); + + // ignore the following classes because they are used in the JavaAgent class + ignoreClasses.add(JavaAgent.class.getCanonicalName()); + ignoreClasses.add(InspectItClassLoader.class.getCanonicalName()); + } + + /** + * Analyze this jar file for containing jar files and classes to be used in our own + * classloader. + * + * @param file + * the file to analyze + * @throws IOException + * if something happens on file access. + */ + private void addJarResource(File file) throws IOException { + JarFile jarFile = new JarFile(file); + addURL(file.toURI().toURL()); + analyzeFile(file); + Enumeration jarEntries = jarFile.entries(); + while (jarEntries.hasMoreElements()) { + JarEntry jarEntry = jarEntries.nextElement(); + if (!jarEntry.isDirectory() && isJar(jarEntry.getName())) { + addJarResource(jarEntryAsFile(jarFile, jarEntry)); + } + } + } + + /** + * If the file name denotes a jar file. + * + * @param fileName + * the file name to define if it is a jar file. + * @return true if the file name denotes a jar file, false otherwise. + */ + private boolean isJar(String fileName) { + return fileName != null && fileName.toLowerCase().endsWith(".jar"); + } + + /** + * The entry in the jar file is a jar file and needs to be extracted into a temporary file + * and analyzed/added to our valid classes. + * + * @param jarFile + * the jar file to get the input stream from. + * @param jarEntry + * the specific jar entry which denotes a jar file. + * @return the temporary file. + * @throws IOException + * if some problems appear while accessing or writing the files. + */ + private File jarEntryAsFile(JarFile jarFile, JarEntry jarEntry) throws IOException { + InputStream input = null; + OutputStream output = null; + try { + String name = jarEntry.getName().replace('/', '_'); + int i = name.lastIndexOf('.'); + String extension = i > -1 ? name.substring(i) : ""; + File file = File.createTempFile(name.substring(0, name.length() - extension.length()) + ".", extension); + file.deleteOnExit(); + input = jarFile.getInputStream(jarEntry); + output = new FileOutputStream(file); + + byte[] buffer = new byte[4096]; + int readCount = input.read(buffer); + while (readCount != -1) { + output.write(buffer, 0, readCount); + readCount = input.read(buffer); + } + + return file; + } finally { + close(input); + close(output); + } + } + + /** + * Analyzes the (jar) file to get the contained classes for our single first approach. + * + * @param file + * the file to analyze. + * @throws IOException + * if there are problems on accessing the file. + */ + private void analyzeFile(File file) throws IOException { + JarFile jarFile = new JarFile(file); + Enumeration jarEntries = jarFile.entries(); + while (jarEntries.hasMoreElements()) { + JarEntry jarEntry = jarEntries.nextElement(); + if (jarEntry.getName().endsWith(".class")) { + selfFirstClasses.add(jarEntry.getName().replaceAll("/", "\\.").replace(".class", "")); + } + } + if (null != jarFile) { + jarFile.close(); + } + } + + /** + * Convenience method to close the Closeable object and ignore the exception being thrown if + * there is one. + * + * @param closeable + * the Closeable object. + */ + private static void close(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + + } catch (IOException e) { // NOPMD NOCHK + // don't care about this one + } + } + } + + /** {@inheritDoc} */ + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + Class result = findLoadedClass(name); + if (null != result) { + if (resolve) { + resolveClass(result); + } + return result; + } + + boolean selfFirst = false; + if (!ignoreClasses.contains(name)) { + if (selfFirstClasses.contains(name)) { + selfFirst = true; + } + } + + // Override parent-first behavior into self-first only for specified classes + if (selfFirst) { + Class c = findClass(name); + if (resolve) { + resolveClass(c); + } + return c; + } else { + return super.loadClass(name, resolve); + } + } + + /** {@inheritDoc} */ + @Override + protected PermissionCollection getPermissions(CodeSource codesource) { + // apply the all permission policy to all of our classes and packages. + AllPermission allPerm = new AllPermission(); + PermissionCollection pc = allPerm.newPermissionCollection(); + pc.add(allPerm); + return pc; + } + + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/jrebel/JRebelUtil.java b/Agent/src/info/novatec/inspectit/agent/jrebel/JRebelUtil.java new file mode 100644 index 000000000..ca921a76c --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/jrebel/JRebelUtil.java @@ -0,0 +1,105 @@ +package info.novatec.inspectit.agent.jrebel; + +import info.novatec.inspectit.agent.analyzer.IClassPoolAnalyzer; +import info.novatec.inspectit.agent.analyzer.IInheritanceAnalyzer; +import info.novatec.inspectit.agent.config.impl.PropertyAccessor.PropertyPathStart; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; +import info.novatec.inspectit.communication.data.ParameterContentType; + +import org.apache.commons.collections.CollectionUtils; + +/** + * Small utility provide JRebel compatibility. + * + * @author Ivan Senic + * + */ +public final class JRebelUtil { + + /** + * The method that substitutes the constructor in the jRebel created classes. + */ + private static final String JREBEL_CONSTRUCTOR_INIT_METHOD = "__init__"; + + /** + * Suffix added to every jRebel created class. + */ + private static final String JREBEL_SUFFIX_PATTERN = "$$M$*"; + + /** + * Private constructor for util class. + */ + private JRebelUtil() { + } + + /** + * Returns the JRebel {@link UnregisteredSensorConfig} based on the original + * {@link UnregisteredSensorConfig}. This new configuration should match all the JRebel enhanced + * classes that match the original classes provided by original {@link UnregisteredSensorConfig} + * . + * + * @param original + * Original {@link UnregisteredSensorConfig}. + * @param classPoolAnalyzer + * {@link IClassPoolAnalyzer} + * @param inheritanceAnalyzer + * {@link IInheritanceAnalyzer} + * @return {@link UnregisteredSensorConfig} that matches the new/updated JRebel classes. + */ + public static UnregisteredSensorConfig getJRebelSensorConfiguration(UnregisteredSensorConfig original, IClassPoolAnalyzer classPoolAnalyzer, IInheritanceAnalyzer inheritanceAnalyzer) { + UnregisteredSensorConfig jRebelSensorConfig = new UnregisteredSensorConfig(classPoolAnalyzer, inheritanceAnalyzer); + + // class name must end with jrebel suffix pattern + jRebelSensorConfig.setTargetPackageName(original.getTargetPackageName()); + jRebelSensorConfig.setTargetClassName(original.getTargetClassName() + JREBEL_SUFFIX_PATTERN); + + // if we have a constructor then we need to instrument the init method in fact + if (original.isConstructor()) { + jRebelSensorConfig.setTargetMethodName(JREBEL_CONSTRUCTOR_INIT_METHOD); + } else { + jRebelSensorConfig.setTargetMethodName(original.getTargetMethodName()); + } + + // if we are not ignoring the signature of methods then add the original class name as first + // parameter + if (!original.isIgnoreSignature()) { + jRebelSensorConfig.getParameterTypes().add(original.getTargetClassName()); + jRebelSensorConfig.getParameterTypes().addAll(original.getParameterTypes()); + } + + // constructor is always false + jRebelSensorConfig.setConstructor(false); + + // copy other stuff + jRebelSensorConfig.setIgnoreSignature(original.isIgnoreSignature()); + jRebelSensorConfig.setExceptionSensorActivated(original.isExceptionSensorActivated()); + jRebelSensorConfig.setSuperclass(original.isSuperclass()); + jRebelSensorConfig.setInterface(original.isInterface()); + jRebelSensorConfig.setAnnotationClassName(original.getAnnotationClassName()); + jRebelSensorConfig.setModifiers(original.getModifiers()); + jRebelSensorConfig.getSettings().putAll(original.getSettings()); + jRebelSensorConfig.setIgnoreSignature(original.isIgnoreSignature()); + jRebelSensorConfig.setVirtual(true); + jRebelSensorConfig.setSensorTypeConfig(original.getSensorTypeConfig()); + + // if property path is ParameterContentType.PARAM, we must increase the signature position + // by 1, since all parameters are shifted by 1 + if (CollectionUtils.isNotEmpty(original.getPropertyAccessorList())) { + for (PropertyPathStart pathStart : original.getPropertyAccessorList()) { + if (pathStart.getContentType() != ParameterContentType.PARAM) { + jRebelSensorConfig.getPropertyAccessorList().add(pathStart); + } else { + PropertyPathStart newPathStart = new PropertyPathStart(); + newPathStart.setName(pathStart.getName()); + newPathStart.setContentType(pathStart.getContentType()); + newPathStart.setSignaturePosition(pathStart.getSignaturePosition() + 1); + newPathStart.setPathToContinue(pathStart.getPathToContinue()); + jRebelSensorConfig.getPropertyAccessorList().add(newPathStart); + } + } + } + jRebelSensorConfig.setPropertyAccess(original.isPropertyAccess()); + + return jRebelSensorConfig; + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/logback/LogInitializer.java b/Agent/src/info/novatec/inspectit/agent/logback/LogInitializer.java new file mode 100644 index 000000000..5ba8e936b --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/logback/LogInitializer.java @@ -0,0 +1,138 @@ +package info.novatec.inspectit.agent.logback; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.PropertyDefinerBase; +import ch.qos.logback.core.joran.spi.JoranException; +import ch.qos.logback.core.util.StatusPrinter; + +/** + * The component responsible for log initializations. + * + * @author Ivan Senic + * + */ +public final class LogInitializer extends PropertyDefinerBase { + + /** + * Default name of the log file. + */ + public static final String DEFAULT_LOG_FILE_NAME = "logging-config.xml"; + + /** + * JVM property for the log file location. + */ + private static final String LOG_FILE_PROPERTY = "inspectit.logging.config"; + + /** + * Location of logs. + */ + private static String logDirLocation; + + /** + * Location of inspectIT jar file. + */ + private static String inspectitJarLocation; + + /** + * Initializes the logging. + */ + public static void initLogging() { + if (null == inspectitJarLocation) { + return; + } + + // set the location of logs + File agentJar = new File(inspectitJarLocation).getAbsoluteFile(); + + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + context.reset(); + + InputStream is = null; + + try { + // first check if it's supplied as parameter + String logFileLocation = System.getProperty(LOG_FILE_PROPERTY); + if (null != logFileLocation) { + File logFile = new File(logFileLocation).getAbsoluteFile(); + if (logFile.exists()) { + is = new FileInputStream(logFile); + } + } + + // then fail to default if none is specified + if (null == is && null != agentJar) { + String logPath = agentJar.getParent() + File.separator + File.separator + DEFAULT_LOG_FILE_NAME; + File logFile = new File(logPath); + if (logFile.exists()) { + is = new FileInputStream(logFile); + } + } + + if (null != is) { + try { + configurator.doConfigure(is); + } catch (JoranException e) { // NOPMD NOCHK StatusPrinter will handle this + } finally { + is.close(); + } + } + } catch (IOException e) { // NOPMD NOCHK StatusPrinter will handle this + } + + StatusPrinter.printInCaseOfErrorsOrWarnings(context); + } + + /** + * Sets the agent name and re-initializes the logging so that the agentName is used as folder + * for logging. + * + * @param agentName + * Agent name. + */ + public static void setAgentNameAndInitLogging(String agentName) { + if (null == inspectitJarLocation) { + return; + } + + // set the location of logs based to agent name + File agentJar = new File(inspectitJarLocation).getAbsoluteFile(); + logDirLocation = agentJar.getParent() + File.separator + "logs" + File.separator + agentName; + + initLogging(); + } + + /** + * Sets {@link #inspectitJarLocation}. + * + * @param inspectitJarLoc + * New value for {@link #inspectitJarLocation} + */ + public static void setInspectitJarLocation(String inspectitJarLoc) { + inspectitJarLocation = inspectitJarLoc; + + // set the location of logs to just [agent-path]/logs/startup for start + File agentJar = new File(inspectitJarLocation).getAbsoluteFile(); + logDirLocation = agentJar.getParent() + File.separator + "logs" + File.separator + "startup"; + } + + /** + * {@inheritDoc} + *

+ * Returns {@link #logDirLocation} if one is set. + */ + public String getPropertyValue() { + return logDirLocation; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sending/AbstractSendingStrategy.java b/Agent/src/info/novatec/inspectit/agent/sending/AbstractSendingStrategy.java new file mode 100644 index 000000000..a5df2624f --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sending/AbstractSendingStrategy.java @@ -0,0 +1,79 @@ +package info.novatec.inspectit.agent.sending; + +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.config.impl.StrategyConfig; +import info.novatec.inspectit.agent.core.ICoreService; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Every send strategy has to extend this abstract class. The first method that is called after + * creating an instance is {@link #start()}. An event listener or starting a thread has to be + * implemented there. {@link #stop()} immediately stops the strategy. {@link #reset()} is called + * after a successful {@link #sendNow()} is executed. + * + * @author Patrice Bouillet + * + */ +public abstract class AbstractSendingStrategy implements ISendingStrategy, InitializingBean { + + /** + * The {@link ICoreService} implementation. Needed to actually trigger the sending of the data. + */ + private ICoreService coreService; + + /** + * Configuration storage to read settings from. + */ + @Autowired + private IConfigurationStorage configurationStorage; + + /** + * Send the data to the server. + */ + protected final void sendNow() { + coreService.sendData(); + } + + /** + * {@inheritDoc} + */ + public final void start(ICoreService coreService) { + this.coreService = coreService; + startStrategy(); + } + + /** + * This method has to be implemented by every strategy concerning the sending process. It will + * start the strategy. + */ + protected abstract void startStrategy(); + + /** + * {@inheritDoc} + */ + public abstract void stop(); + + /** + * Returns the value storage. + * + * @return The value storage implementation. + */ + protected final ICoreService getCoreService() { + return coreService; + } + + /** + * {@inheritDoc} + */ + public void afterPropertiesSet() throws Exception { + for (StrategyConfig sendingStrategyConfig : configurationStorage.getSendingStrategyConfigs()) { + if (sendingStrategyConfig.getClazzName().equals(this.getClass().getName())) { + this.init(sendingStrategyConfig.getSettings()); + break; + } + } + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sending/ISendingStrategy.java b/Agent/src/info/novatec/inspectit/agent/sending/ISendingStrategy.java new file mode 100644 index 000000000..cd04afc7e --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sending/ISendingStrategy.java @@ -0,0 +1,39 @@ +package info.novatec.inspectit.agent.sending; + +import info.novatec.inspectit.agent.core.ICoreService; + +import java.util.Map; + +/** + * All sending strategies are first initialized via the {@link #init(Map)} method. Afterwards, the + * sending strategy has to be started manually with {@link #start(ICoreService)}. + * + * @author Patrice Bouillet + * + */ +public interface ISendingStrategy { + + /** + * Start the strategy. + * + * @param coreService + * The core service reference is needed for the strategy to fire the event that the + * core service should send its measurements now. + */ + void start(ICoreService coreService); + + /** + * Stop the strategy. + */ + void stop(); + + /** + * Initializes the abstract strategy object and stores a reference to an {@link ICoreService} + * implementation. + * + * @param settings + * Settings saved in a {@link Map}. Will be redirected to {@link #initStrategy(Map)}. + */ + void init(Map settings); + +} \ No newline at end of file diff --git a/Agent/src/info/novatec/inspectit/agent/sending/impl/ListSizeStrategy.java b/Agent/src/info/novatec/inspectit/agent/sending/impl/ListSizeStrategy.java new file mode 100644 index 000000000..617518ee9 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sending/impl/ListSizeStrategy.java @@ -0,0 +1,59 @@ +package info.novatec.inspectit.agent.sending.impl; + +import info.novatec.inspectit.agent.core.ListListener; +import info.novatec.inspectit.agent.sending.AbstractSendingStrategy; +import info.novatec.inspectit.communication.DefaultData; + +import java.util.List; +import java.util.Map; + +/** + * A simple implementation which checks the size of the list of the current value objects. If the + * size of the list is greater than the defined one, {@link #sendNow()} is called. + * + * @author Patrice Bouillet + * + */ +public class ListSizeStrategy extends AbstractSendingStrategy implements ListListener> { + + /** + * Default size. + */ + private static final long DEFAULT_SIZE = 10L; + + /** + * The size. + */ + private long size = DEFAULT_SIZE; + + /** + * {@inheritDoc} + */ + public void startStrategy() { + getCoreService().addListListener(this); + } + + /** + * {@inheritDoc} + */ + public void stop() { + getCoreService().removeListListener(this); + } + + /** + * {@inheritDoc} + */ + public void contentChanged(List> list) { + if (list.size() > size) { + sendNow(); + } + } + + /** + * {@inheritDoc} + */ + public void init(Map settings) { + this.size = Long.parseLong((String) settings.get("size")); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sending/impl/TimeStrategy.java b/Agent/src/info/novatec/inspectit/agent/sending/impl/TimeStrategy.java new file mode 100644 index 000000000..300d00575 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sending/impl/TimeStrategy.java @@ -0,0 +1,102 @@ +package info.novatec.inspectit.agent.sending.impl; + +import info.novatec.inspectit.agent.sending.AbstractSendingStrategy; + +import java.util.Map; + +/** + * Implements a strategy to wait a specific (user-defined) time and then executes the sending of the + * data. + * + * @author Patrice Bouillet + * + */ +public class TimeStrategy extends AbstractSendingStrategy { + + /** + * The default wait time. + */ + public static final long DEFAULT_WAIT_TIME = 5000L; + + /** + * The wait time. + */ + private long time = DEFAULT_WAIT_TIME; + + /** + * The thread which waits the specified time and starts the sending process. + */ + private volatile Trigger trigger; + + /** + * If we are allowed to send something right now. + */ + private boolean allowSending = true; + + /** + * {@inheritDoc} + */ + public void startStrategy() { + trigger = new Trigger(); + trigger.start(); + } + + /** + * {@inheritDoc} + */ + public void stop() { + // Interrupt the thread to stop it + Thread temp = trigger; + trigger = null; // NOPMD + synchronized (temp) { + temp.interrupt(); + } + } + + /** + * The Trigger class is basically a {@link Thread} which starts the sending process once the + * specified time is passed by. + * + * @author Patrice Bouillet + * + */ + private class Trigger extends Thread { + + /** + * Creates a new Trigger as daemon thread. + */ + public Trigger() { + setName("inspectit-timer-strategy-trigger-thread"); + setDaemon(true); + } + + /** + * {@inheritDoc} + */ + public void run() { + Thread thisThread = Thread.currentThread(); + while (trigger == thisThread) { // NOPMD + try { + synchronized (this) { + wait(time); + } + + if (allowSending) { + sendNow(); + } + } catch (InterruptedException e) { // NOCHK + // nothing to do + } + } + } + + } + + /** + * {@inheritDoc} + */ + public void init(Map settings) { + this.time = Long.parseLong(settings.get("time")); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/ISensor.java b/Agent/src/info/novatec/inspectit/agent/sensor/ISensor.java new file mode 100644 index 000000000..29f2eb183 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/ISensor.java @@ -0,0 +1,23 @@ +package info.novatec.inspectit.agent.sensor; + +import java.util.Map; + +/** + * This interface is used by all sensor which are collecting any kind of data. The + * {@link #init(Map)} method is used to initialize the sensor with some additional preferences if + * available. + * + * @author Patrice Bouillet + * + */ +public interface ISensor { + + /** + * Initialize the sensor. + * + * @param parameter + * Some additional parameters. + */ + void init(Map parameter); + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/exception/ExceptionSensor.java b/Agent/src/info/novatec/inspectit/agent/sensor/exception/ExceptionSensor.java new file mode 100644 index 000000000..baa5c1e26 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/exception/ExceptionSensor.java @@ -0,0 +1,60 @@ +package info.novatec.inspectit.agent.sensor.exception; + +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.hooking.IHook; +import info.novatec.inspectit.agent.sensor.method.AbstractMethodSensor; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; + +/** + * The {@link ExceptionSensor} which initializes and returns the {@link ExceptionSensorHook} class. + * + * @author Eduard Tudenhoefner + * + */ +public class ExceptionSensor extends AbstractMethodSensor implements IExceptionSensor { + + /** + * The ID manager. + */ + @Autowired + private IIdManager idManager; + + /** + * The used exception sensor hook. + */ + private ExceptionSensorHook exceptionSensorHook = null; + + /** + * No-arg constructor needed for Spring. + */ + public ExceptionSensor() { + } + + /** + * The default constructor which needs 3 parameter for initialization. + * + * @param idManager + * The ID manager. + */ + public ExceptionSensor(IIdManager idManager) { + this.idManager = idManager; + } + + /** + * {@inheritDoc} + */ + public IHook getHook() { + return exceptionSensorHook; + } + + /** + * {@inheritDoc} + */ + public void init(Map parameter) { + exceptionSensorHook = new ExceptionSensorHook(idManager, parameter); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/exception/ExceptionSensorHook.java b/Agent/src/info/novatec/inspectit/agent/sensor/exception/ExceptionSensorHook.java new file mode 100644 index 000000000..0d9ea437f --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/exception/ExceptionSensorHook.java @@ -0,0 +1,294 @@ +package info.novatec.inspectit.agent.sensor.exception; + +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.communication.ExceptionEvent; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.util.StringConstraint; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.sql.Timestamp; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class adds additional code to a constructor of type {@link Throwable}, to the + * throw statement and to the catch block catching type {@link Throwable}. + * + * @author Eduard Tudenhoefner + * @see IExceptionSensorHook + * + */ +public class ExceptionSensorHook implements IExceptionSensorHook { + + /** + * The logger of this class. Initialized manually. + */ + private static final Logger LOG = LoggerFactory.getLogger(ExceptionSensorHook.class); + + /** + * The ID manager. + */ + private final IIdManager idManager; + + /** + * The thread local containing the {@link IdentityHashToDataObject} object. + */ + private ThreadLocal exceptionDataHolder = new ThreadLocal(); + + /** + * The thread local containing the id of the method where the exception was handled. + */ + private ThreadLocal exceptionHandlerId = new ThreadLocal(); + + /** + * The StringConstraint to ensure a maximum length of strings. + */ + private StringConstraint strConstraint; + + /** + * The default constructor which needs one parameter for initialization. + * + * @param idManager + * The ID manager. + * @param parameter + * Additional parameters. + */ + public ExceptionSensorHook(IIdManager idManager, Map parameter) { + this.idManager = idManager; + this.strConstraint = new StringConstraint(parameter); + } + + /** + * {@inheritDoc} + */ + public void beforeConstructor(long methodId, long sensorTypeId, Object[] parameters, RegisteredSensorConfig rsc) { + // nothing to do here + } + + /** + * {@inheritDoc} + */ + public void afterConstructor(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc) { + // getting the actual object class and comparing to the registered sensor config target + // class + String throwableClass = object.getClass().getName(); + String rscTragetClassname = rsc.getQualifiedTargetClassName(); + if (throwableClass.equals(rscTragetClassname)) { + try { + long platformId = idManager.getPlatformId(); + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + long registeredConstructorId = idManager.getRegisteredMethodId(methodId); + long registeredSensorTypeId = idManager.getRegisteredSensorTypeId(sensorTypeId); + Long identityHash = Long.valueOf(System.identityHashCode(object)); + + // need to reset the exception handler id + exceptionHandlerId.set(null); + + // getting the actual object with information + Throwable throwable = (Throwable) object; + + // creating the data object + ExceptionSensorData data = new ExceptionSensorData(timestamp, platformId, registeredSensorTypeId, registeredConstructorId); + data.setThrowableIdentityHashCode(identityHash.longValue()); + data.setExceptionEvent(ExceptionEvent.CREATED); + data.setThrowableType(throwable.getClass().getName()); + + // set the static information of the current object + setStaticInformation(data, throwable); + + // creating the mapping object and setting it on the thread local + exceptionDataHolder.set(new IdentityHashToDataObject(identityHash, data)); + + // adding the data object to the core service + coreService.addExceptionSensorData(registeredSensorTypeId, data.getThrowableIdentityHashCode(), data); + } catch (IdNotAvailableException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Could not start exception sequence because of a (currently) not mapped ID"); + } + } + } + } + + /** + * {@inheritDoc} + */ + public void dispatchOnThrowInBody(ICoreService coreService, long id, long sensorTypeId, Object object, Object exceptionObject, Object[] parameters, RegisteredSensorConfig rsc) { + // get the mapping object + IdentityHashToDataObject mappingObject = exceptionDataHolder.get(); + + if (null != mappingObject) { + try { + long platformId = idManager.getPlatformId(); + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + long registeredMethodId = idManager.getRegisteredMethodId(id); + long registeredSensorTypeId = idManager.getRegisteredSensorTypeId(sensorTypeId); + Long identityHash = Long.valueOf(System.identityHashCode(exceptionObject)); + + // getting the actual object with information + Throwable throwable = (Throwable) exceptionObject; + + // creating the data object + ExceptionSensorData data = new ExceptionSensorData(timestamp, platformId, registeredSensorTypeId, registeredMethodId); + data.setThrowableIdentityHashCode(identityHash.longValue()); + data.setThrowableType(throwable.getClass().getName()); + + // check whether it's the same Throwable object as before + if (mappingObject.getIdentityHash().equals(identityHash)) { + // we have to check whether the Throwable object is just passed or explicitly + // rethrown + if (null != exceptionHandlerId.get() && registeredMethodId == exceptionHandlerId.get().longValue()) { + // the Throwable object is explicitly rethrown + data.setExceptionEvent(ExceptionEvent.RETHROWN); + } else { + // the Throwable object is thrown the first time or just passed by the JVM, + // so it's a PASSED event + data.setExceptionEvent(ExceptionEvent.PASSED); + } + + // current object is the child of the previous object + ExceptionSensorData parent = mappingObject.getExceptionSensorData(); + parent.setChild(data); + + // we are just exchanging the data object and setting it on the mapping object + mappingObject.setExceptionSensorData(data); + exceptionDataHolder.set(mappingObject); + } else { + // it's a new Throwable object, that we didn't recognize earlier + data.setExceptionEvent(ExceptionEvent.UNREGISTERED_PASSED); + setStaticInformation(data, throwable); + + // we are creating a new mapping object and setting it on the thread local + exceptionDataHolder.set(new IdentityHashToDataObject(identityHash, data)); + } + + // adding the data object to the core service + coreService.addExceptionSensorData(registeredSensorTypeId, data.getThrowableIdentityHashCode(), data); + } catch (IdNotAvailableException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Could not start exception sequence because of a (currently) not mapped ID"); + } + } + } + } + + /** + * {@inheritDoc} + */ + public void dispatchBeforeCatchBody(ICoreService coreService, long id, long sensorTypeId, Object exceptionObject, RegisteredSensorConfig rsc) { + // get the mapping object + IdentityHashToDataObject mappingObject = exceptionDataHolder.get(); + + if (null != mappingObject) { + try { + long platformId = idManager.getPlatformId(); + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + long registeredMethodId = idManager.getRegisteredMethodId(id); + long registeredSensorTypeId = idManager.getRegisteredSensorTypeId(sensorTypeId); + Long identityHash = Long.valueOf(System.identityHashCode(exceptionObject)); + + // save id of the method where the exception is catched + exceptionHandlerId.set(Long.valueOf(registeredMethodId)); + + // getting the actual object with information + Throwable throwable = (Throwable) exceptionObject; + + // creating the data object + ExceptionSensorData data = new ExceptionSensorData(timestamp, platformId, registeredSensorTypeId, registeredMethodId); + data.setThrowableIdentityHashCode(identityHash.longValue()); + data.setThrowableType(throwable.getClass().getName()); + data.setExceptionEvent(ExceptionEvent.HANDLED); + + // check whether it's the same Throwable object as before + if (mappingObject.getIdentityHash().equals(identityHash)) { + // current object is the child of the previous object + ExceptionSensorData parent = mappingObject.getExceptionSensorData(); + parent.setChild(data); + + // we are just exchanging the data object and setting it on the mapping object + mappingObject.setExceptionSensorData(data); + exceptionDataHolder.set(mappingObject); + } else { + // it's a Throwable object, that we didn't recognize earlier + data.setExceptionEvent(ExceptionEvent.UNREGISTERED_PASSED); + setStaticInformation(data, throwable); + + // we are creating a new mapping object and setting it on the thread local + exceptionDataHolder.set(new IdentityHashToDataObject(identityHash, data)); + } + + // adding the data object to the core service + coreService.addExceptionSensorData(registeredSensorTypeId, data.getThrowableIdentityHashCode(), data); + } catch (IdNotAvailableException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Could not start exception sequence because of a (currently) not mapped ID"); + } + } + } + } + + /** + * Gets static information (class name, stackTrace, cause) from the {@link Throwable} object and + * sets them on the passed data object. + * + * @param exceptionSensorData + * The {@link ExceptionSensorData} object where to set the information. + * @param throwable + * The current {@link Throwable} object where to get the information. + */ + private void setStaticInformation(ExceptionSensorData exceptionSensorData, Throwable throwable) { + // see INSPECTIT-387 + // getting the static content could not be always possible due to the fact that this method + // can be executed before the creation of the concrete exception object. Getting some + // of the information could be done by overriding, meaning that we are executing the + // methods on the object which creation is still not finished. This can cause exceptions + // that we need to handle properly + + try { + Throwable cause = throwable.getCause(); + if (null != cause) { + exceptionSensorData.setCause(strConstraint.crop(cause.getClass().getName())); + } + } catch (Exception e) { + if (LOG.isDebugEnabled()) { + LOG.debug("It was not possible to retrieve the exception cause from " + throwable.getClass().getName(), e); + } + } + + try { + exceptionSensorData.setErrorMessage(strConstraint.crop(throwable.getMessage())); + } catch (Exception e) { + if (LOG.isDebugEnabled()) { + LOG.debug("It was not possible to retrieve the error message from " + throwable.getClass().getName(), e); + } + } + + try { + exceptionSensorData.setStackTrace(strConstraint.crop(stackTraceToString(throwable))); + } catch (Exception e) { + if (LOG.isDebugEnabled()) { + LOG.debug("It was not possible to retrieve the stack trace from " + throwable.getClass().getName(), e); + } + } + } + + /** + * Gets the stack trace from the {@link Throwable} object and returns it as a string. + * + * @param throwable + * The {@link Throwable} object where to get the stack trace from. + * @return A string representation of a stack trace. + */ + private String stackTraceToString(Throwable throwable) { + Writer result = new StringWriter(); + PrintWriter writer = new PrintWriter(result); + throwable.printStackTrace(writer); + return result.toString(); + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/exception/IExceptionSensor.java b/Agent/src/info/novatec/inspectit/agent/sensor/exception/IExceptionSensor.java new file mode 100644 index 000000000..ffbf12295 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/exception/IExceptionSensor.java @@ -0,0 +1,13 @@ +package info.novatec.inspectit.agent.sensor.exception; + +import info.novatec.inspectit.agent.sensor.method.IMethodSensor; + +/** + * The main interface for all exception sensors. + * + * @author Eduard Tudenhoefner + * + */ +public interface IExceptionSensor extends IMethodSensor { + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/exception/IExceptionSensorHook.java b/Agent/src/info/novatec/inspectit/agent/sensor/exception/IExceptionSensorHook.java new file mode 100644 index 000000000..20dc22ce2 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/exception/IExceptionSensorHook.java @@ -0,0 +1,59 @@ +package info.novatec.inspectit.agent.sensor.exception; + +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.hooking.IConstructorHook; + +/** + * Classes which add additional instructions to the constructor of type {@link Throwable}, to the + * throw statement and to the catch block catching type {@link Throwable} + * have to implement this interface. + * + * @author Eduard Tudenhoefner + * + */ +public interface IExceptionSensorHook extends IConstructorHook { + + /** + * This method is executed when an object of type {@link Throwable} is thrown in a method body + * with the throw statement. This method is wrapping the thrower method with a + * try-catch block and gets the needed information. After that the {@link Throwable} object is + * thrown from that method to propagate along the normal exceptional path. + * + * @param coreService + * The core service. + * @param id + * The method id where the {@link Throwable} object was thrown. + * @param sensorTypeId + * The sensor type id of the {@link IExceptionSensor}. + * @param object + * The class itself which contains the hook. + * @param exceptionObject + * The exception type that occured in the method body. + * @param parameters + * The parameters of the method call. + * @param rsc + * The {@link RegisteredSensorConfig} containing all information about the method + * where the {@link Throwable} object was thrown. + */ + void dispatchOnThrowInBody(ICoreService coreService, long id, long sensorTypeId, Object object, Object exceptionObject, Object[] parameters, RegisteredSensorConfig rsc); + + /** + * This method is executed just before a handler (appropriate catch block) for the thrown + * {@link Throwable} object is executed. + * + * @param coreService + * The core service. + * @param id + * The method id where the {@link Throwable} object was handled. + * @param sensorTypeId + * The sensor type id of the {@link IExceptionSensor}. + * @param exceptionObject + * The {@link Throwable} object itself. + * @param rsc + * The {@link RegisteredSensorConfig} containing all information about the method + * where the {@link Throwable} object was thrown. + */ + void dispatchBeforeCatchBody(ICoreService coreService, long id, long sensorTypeId, Object exceptionObject, RegisteredSensorConfig rsc); + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/exception/IdentityHashToDataObject.java b/Agent/src/info/novatec/inspectit/agent/sensor/exception/IdentityHashToDataObject.java new file mode 100644 index 000000000..dbf77a7cb --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/exception/IdentityHashToDataObject.java @@ -0,0 +1,80 @@ +package info.novatec.inspectit.agent.sensor.exception; + +import info.novatec.inspectit.communication.data.ExceptionSensorData; + +/** + * This class maps the identity hashcode of a {@link Throwable} object to the + * {@link ExceptionSensorData} object. + * + * @author Eduard Tudenhoefner + * + */ +public class IdentityHashToDataObject { + /** + * The identity hashcode of the {@link Throwable} object. + */ + private Long identityHash; + + /** + * The {@link ExceptionSensorData} object containing all information. + */ + private ExceptionSensorData exceptionSensorData; + + /** + * Default no-arg constructor. + */ + public IdentityHashToDataObject() { + } + + /** + * Constructor taking the identity hashcode and the {@link ExceptionSensorData} object for + * initialization. + * + * @param identityHash + * The identity hashcode of the {@link Throwable} object. + * @param exceptionSensorData + * The {@link ExceptionSensorData} object containing the information. + */ + public IdentityHashToDataObject(Long identityHash, ExceptionSensorData exceptionSensorData) { + this.identityHash = identityHash; + this.exceptionSensorData = exceptionSensorData; + } + + /** + * Returns the identity hashcode of the {@link Throwable} object. + * + * @return The identity hashcode of the {@link Throwable} object. + */ + public Long getIdentityHash() { + return identityHash; + } + + /** + * Sets the identity hashcode of the {@link Throwable} object. + * + * @param identityHash + * The identity hashcode of the {@link Throwable} object. + */ + public void setIdentityHash(Long identityHash) { + this.identityHash = identityHash; + } + + /** + * Returns the {@link ExceptionSensorData} object containing the information. + * + * @return The {@link ExceptionSensorData} object containing the information. + */ + public ExceptionSensorData getExceptionSensorData() { + return exceptionSensorData; + } + + /** + * Sets the {@link ExceptionSensorData} object. + * + * @param exceptionSensorData + * The {@link ExceptionSensorData} object containing the information. + */ + public void setExceptionSensorData(ExceptionSensorData exceptionSensorData) { + this.exceptionSensorData = exceptionSensorData; + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/AbstractMethodSensor.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/AbstractMethodSensor.java new file mode 100644 index 000000000..fb4a52210 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/AbstractMethodSensor.java @@ -0,0 +1,37 @@ +package info.novatec.inspectit.agent.sensor.method; + +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.config.impl.MethodSensorTypeConfig; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Abstract class for all {@link IMethodSensor}s to properly initialize after Spring has set all the + * properties. + * + * @author Ivan Senic + * + */ +public abstract class AbstractMethodSensor implements InitializingBean, IMethodSensor { + + /** + * Configuration storage for initializing the sensor and registering with the config. + */ + @Autowired + private IConfigurationStorage configurationStorage; + + /** + * {@inheritDoc} + */ + public void afterPropertiesSet() throws Exception { + for (MethodSensorTypeConfig config : configurationStorage.getMethodSensorTypes()) { + if (config.getClassName().equals(this.getClass().getName())) { + this.init(config.getParameters()); + config.setSensorType(this); + break; + } + } + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/IMethodSensor.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/IMethodSensor.java new file mode 100755 index 000000000..f75be10b1 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/IMethodSensor.java @@ -0,0 +1,23 @@ +package info.novatec.inspectit.agent.sensor.method; + +import info.novatec.inspectit.agent.hooking.IHook; +import info.novatec.inspectit.agent.hooking.IMethodHook; +import info.novatec.inspectit.agent.sensor.ISensor; + +/** + * Every method sensor installs a hook into the target class which can be retrieved later with the + * {@link #getHook()} method. + * + * @author Patrice Bouillet + * + */ +public interface IMethodSensor extends ISensor { + + /** + * Returns the proper method hook. + * + * @return The {@link IMethodHook} implementation of the corresponding sensor. + */ + IHook getHook(); + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/averagetimer/AverageTimerHook.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/averagetimer/AverageTimerHook.java new file mode 100644 index 000000000..064720f9f --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/averagetimer/AverageTimerHook.java @@ -0,0 +1,166 @@ +package info.novatec.inspectit.agent.sensor.method.averagetimer; + +import info.novatec.inspectit.agent.config.IPropertyAccessor; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.core.impl.CoreService; +import info.novatec.inspectit.agent.hooking.IConstructorHook; +import info.novatec.inspectit.agent.hooking.IMethodHook; +import info.novatec.inspectit.communication.data.ParameterContentData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.util.StringConstraint; +import info.novatec.inspectit.util.ThreadLocalStack; +import info.novatec.inspectit.util.Timer; + +import java.sql.Timestamp; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The hook implementation for the average timer sensor. It uses the {@link ThreadLocalStack} class + * to save the time when the method was called. After the complete original method was executed, it + * computes the how long the method took to finish. Afterwards, the measurement is added to the + * {@link CoreService}. + * + * @author Patrice Bouillet + * + */ +public class AverageTimerHook implements IMethodHook, IConstructorHook { + + /** + * The logger of this class. Initialized manually. + */ + private static final Logger LOG = LoggerFactory.getLogger(AverageTimerHook.class); + + /** + * The stack containing the start time values. + */ + private ThreadLocalStack timeStack = new ThreadLocalStack(); + + /** + * The timer used for accurate measuring. + */ + private final Timer timer; + + /** + * The ID manager. + */ + private final IIdManager idManager; + + /** + * The property accessor. + */ + private final IPropertyAccessor propertyAccessor; + + /** + * The StringConstraint to ensure a maximum length of strings. + */ + private StringConstraint strConstraint; + + /** + * The only constructor which needs the {@link Timer}. + * + * @param timer + * The timer. + * @param idManager + * The ID manager. + * @param propertyAccessor + * The property accessor. + * @param param + * Additional parameters. + */ + public AverageTimerHook(Timer timer, IIdManager idManager, IPropertyAccessor propertyAccessor, Map param) { + this.timer = timer; + this.idManager = idManager; + this.propertyAccessor = propertyAccessor; + this.strConstraint = new StringConstraint(param); + } + + /** + * {@inheritDoc} + */ + public void beforeBody(long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc) { + timeStack.push(new Double(timer.getCurrentTime())); + } + + /** + * {@inheritDoc} + */ + public void firstAfterBody(long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) { + timeStack.push(new Double(timer.getCurrentTime())); + } + + /** + * {@inheritDoc} + */ + public void secondAfterBody(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) { + double endTime = timeStack.pop().doubleValue(); + double startTime = timeStack.pop().doubleValue(); + double duration = endTime - startTime; + + List parameterContentData = null; + String prefix = null; + // check if some properties need to be accessed and saved + if (rsc.isPropertyAccess()) { + parameterContentData = propertyAccessor.getParameterContentData(rsc.getPropertyAccessorList(), object, parameters, result); + prefix = parameterContentData.toString(); + + // crop the content strings of all ParameterContentData but leave the prefix as it is + for (ParameterContentData contentData : parameterContentData) { + contentData.setContent(strConstraint.crop(contentData.getContent())); + } + } + + TimerData timerData = (TimerData) coreService.getMethodSensorData(sensorTypeId, methodId, prefix); + + if (null == timerData) { + try { + long platformId = idManager.getPlatformId(); + long registeredSensorTypeId = idManager.getRegisteredSensorTypeId(sensorTypeId); + long registeredMethodId = idManager.getRegisteredMethodId(methodId); + + Timestamp timestamp = new Timestamp(System.currentTimeMillis() - Math.round(duration)); + + timerData = new TimerData(timestamp, platformId, registeredSensorTypeId, registeredMethodId, parameterContentData); + timerData.increaseCount(); + timerData.addDuration(duration); + timerData.calculateMin(duration); + timerData.calculateMax(duration); + + coreService.addMethodSensorData(sensorTypeId, methodId, prefix, timerData); + } catch (IdNotAvailableException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Could not save the average timer data because of an unavailable id. " + e.getMessage()); + } + } + } else { + timerData.increaseCount(); + timerData.addDuration(duration); + + timerData.calculateMin(duration); + timerData.calculateMax(duration); + + } + } + + /** + * {@inheritDoc} + */ + public void beforeConstructor(long methodId, long sensorTypeId, Object[] parameters, RegisteredSensorConfig rsc) { + timeStack.push(new Double(timer.getCurrentTime())); + } + + /** + * {@inheritDoc} + */ + public void afterConstructor(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc) { + timeStack.push(new Double(timer.getCurrentTime())); + secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, null, rsc); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/averagetimer/AverageTimerSensor.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/averagetimer/AverageTimerSensor.java new file mode 100644 index 000000000..7522b5976 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/averagetimer/AverageTimerSensor.java @@ -0,0 +1,81 @@ +package info.novatec.inspectit.agent.sensor.method.averagetimer; + +import info.novatec.inspectit.agent.config.IPropertyAccessor; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.hooking.IHook; +import info.novatec.inspectit.agent.sensor.method.AbstractMethodSensor; +import info.novatec.inspectit.agent.sensor.method.IMethodSensor; +import info.novatec.inspectit.util.Timer; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; + +/** + * The average timer sensor which initializes and returns the {@link AverageTimerHook} class. + * + * @author Patrice Bouillet + * + */ +public class AverageTimerSensor extends AbstractMethodSensor implements IMethodSensor { + + /** + * The timer used for accurate measuring. + */ + @Autowired + private Timer timer; + + /** + * The ID manager. + */ + @Autowired + private IIdManager idManager; + + /** + * The property accessor. + */ + @Autowired + private IPropertyAccessor propertyAccessor; + + /** + * The used average timer hook. + */ + private AverageTimerHook averageTimerHook = null; + + /** + * No-arg constructor needed for Spring. + */ + public AverageTimerSensor() { + } + + /** + * The default constructor which needs 3 parameter for initialization. + * + * @param timer + * The timer used for accurate measuring. + * @param idManager + * The ID manager. + * @param propertyAccessor + * The property accessor. + */ + public AverageTimerSensor(Timer timer, IIdManager idManager, IPropertyAccessor propertyAccessor) { + this.timer = timer; + this.idManager = idManager; + this.propertyAccessor = propertyAccessor; + } + + /** + * {@inheritDoc} + */ + public IHook getHook() { + return averageTimerHook; + } + + /** + * {@inheritDoc} + */ + public void init(Map parameter) { + averageTimerHook = new AverageTimerHook(timer, idManager, propertyAccessor, parameter); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/http/HttpHook.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/http/HttpHook.java new file mode 100644 index 000000000..b95fc0a18 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/http/HttpHook.java @@ -0,0 +1,351 @@ +package info.novatec.inspectit.agent.sensor.method.http; + +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.hooking.IMethodHook; +import info.novatec.inspectit.agent.sensor.method.timer.TimerHook; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.util.StringConstraint; +import info.novatec.inspectit.util.ThreadLocalStack; +import info.novatec.inspectit.util.Timer; + +import java.lang.management.ThreadMXBean; +import java.sql.Timestamp; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The hook implementation for the http sensor. It uses the {@link ThreadLocalStack} class to save + * the time when the method was called. + *

+ * This hook measures timer data like the {@link TimerHook} but in addition provides Http + * information. Another difference is that we ensure that only one Http metric per request is + * created. + * + * @author Stefan Siegl + * + */ +public class HttpHook implements IMethodHook { + + /** + * The logger of this class. Initialized manually. + */ + private static final Logger LOG = LoggerFactory.getLogger(HttpHook.class); + + /** + * The stack containing the start time values. + */ + private final ThreadLocalStack timeStack = new ThreadLocalStack(); + + /** + * The timer used for accurate measuring. + */ + private final Timer timer; + + /** + * The ID manager. + */ + private final IIdManager idManager; + + /** + * The thread MX bean. + */ + private ThreadMXBean threadMXBean; + + /** + * Defines if the thread CPU time is supported. + */ + private boolean threadCPUTimeJMXAvailable = false; + + /** + * Defines if the thread CPU time is enabled. + */ + private boolean threadCPUTimeEnabled = false; + + /** + * The stack containing the start time values. + */ + private final ThreadLocalStack threadCpuTimeStack = new ThreadLocalStack(); + + /** + * Extractor for Http parameters. + */ + private HttpRequestParameterExtractor extractor; + + /** + * Configuration setting if session data should be captured. + */ + private final boolean captureSessionData; + + /** + * Expected name of the HttpServletRequest interface. + */ + private static final String HTTP_SERVLET_REQUEST_CLASS = "javax.servlet.http.HttpServletRequest"; + + /** + * Name of Object class. Stored to reduce number of created String objects during comparison. + */ + private static final String OBJECT_CLASS = Object.class.getName(); + + /** + * Whitelist that contains all classes that we already checked if they provide + * HttpServletMetrics and do. We are talking about the class of the ServletRequest here. This + * list is extended if a new Class that provides this interface is found. + */ + private static final CopyOnWriteArrayList> WHITE_LIST = new CopyOnWriteArrayList>(); + + /** + * Blacklist that contains all classes that we already checked if they provide + * HttpServletMetrics and do not. We are talking about the class of the ServletRequest here. + * This list is extended if a new Class that does not provides this interface is found. + */ + private static final CopyOnWriteArrayList> BLACK_LIST = new CopyOnWriteArrayList>(); + + /** + * Helps us to ensure that we only store on http metric per request. + */ + private final StartEndMarker refMarker = new StartEndMarker(); + + /** + * This constructor creates a new instance of a HttpHook. + * + * @param timer + * The timer + * @param idManager + * The id manager + * @param threadMXBean + * the threadMx Bean for cpu timing + * @param parameters + * the map containing the configuration parameters + */ + public HttpHook(Timer timer, IIdManager idManager, Map parameters, ThreadMXBean threadMXBean) { + this.timer = timer; + this.idManager = idManager; + this.threadMXBean = threadMXBean; + this.extractor = new HttpRequestParameterExtractor(new StringConstraint(parameters)); + + if (null != parameters && "true".equals(parameters.get("sessioncapture"))) { + if (LOG.isDebugEnabled()) { + LOG.debug("Enabling session capturing for the http sensor"); + } + captureSessionData = true; + } else { + captureSessionData = false; + } + + try { + // if it is even supported by this JVM + threadCPUTimeJMXAvailable = threadMXBean.isThreadCpuTimeSupported(); + if (threadCPUTimeJMXAvailable) { + // check if its enabled + threadCPUTimeEnabled = threadMXBean.isThreadCpuTimeEnabled(); + if (!threadCPUTimeEnabled) { + // try to enable it + threadMXBean.setThreadCpuTimeEnabled(true); + // check again now if it is enabled now + threadCPUTimeEnabled = threadMXBean.isThreadCpuTimeEnabled(); + } + } + } catch (RuntimeException e) { + // catching the runtime exceptions which could be thrown by the + // above statements. + LOG.warn("Your environment does not support to capture CPU timings."); + } + } + + /** + * {@inheritDoc} + */ + public void beforeBody(long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc) { + + // We mark the invocation of the first servlet and the calls from within it. This way we + // gather information just once (from the first one) and avoid overhead and inconclusive + // information. + + // Only during the first invocation, we make preparations. + if (!refMarker.isMarkerSet()) { + + // We expect the first parameter to be of the type javax.servlet.ServletRequest + // If this is not the case then the configuration was wrong. + if (parameters.length != 0) { + Object httpServletRequest = parameters[0]; + Class servletRequestClass = httpServletRequest.getClass(); + + // Check if metrics interface provided + if (providesHttpMetrics(servletRequestClass)) { + + // We must take the time as soon as we know that we are dealing with an http + // timer. We cannot do that after we read the information from the request + // object because these methods could be instrumented and thus the whole http + // timer would be off - resulting in very strange results. + timeStack.push(new Double(timer.getCurrentTime())); + if (threadCPUTimeEnabled) { + threadCpuTimeStack.push(Long.valueOf(threadMXBean.getCurrentThreadCpuTime())); + } + + // Mark first invocation + refMarker.markCall(); + + } + } + } else { + // Mark sub invocation, first already marked. + refMarker.markCall(); + } + } + + /** + * {@inheritDoc} + */ + public void firstAfterBody(long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) { + + // no invocation marked -> skip + if (!refMarker.isMarkerSet()) { + return; + } + + // remove mark from sub call + refMarker.markEndCall(); + + if (refMarker.matchesFirst()) { + // Get the timer and store it. + timeStack.push(new Double(timer.getCurrentTime())); + if (threadCPUTimeEnabled) { + threadCpuTimeStack.push(Long.valueOf(threadMXBean.getCurrentThreadCpuTime())); + } + } + } + + /** + * {@inheritDoc} + */ + public void secondAfterBody(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) { + + // check if in the right(first) invocation + if (refMarker.isMarkerSet() && refMarker.matchesFirst()) { + // call ended, remove the marker. + refMarker.remove(); + + // double check if nothing changed + if (parameters.length != 0) { + + Object httpServletRequest = parameters[0]; + Class servletRequestClass = httpServletRequest.getClass(); + + // double check interface + if (providesHttpMetrics(servletRequestClass)) { + + try { + double endTime = ((Double) timeStack.pop()).doubleValue(); + double startTime = ((Double) timeStack.pop()).doubleValue(); + double duration = endTime - startTime; + + // default setting to a negative number + double cpuDuration = -1.0d; + if (threadCPUTimeEnabled) { + long cpuEndTime = ((Long) threadCpuTimeStack.pop()).longValue(); + long cpuStartTime = ((Long) threadCpuTimeStack.pop()).longValue(); + cpuDuration = (cpuEndTime - cpuStartTime) / 1000000.0d; + } + + long platformId = idManager.getPlatformId(); + long registeredSensorTypeId = idManager.getRegisteredSensorTypeId(sensorTypeId); + long registeredMethodId = idManager.getRegisteredMethodId(methodId); + Timestamp timestamp = new Timestamp(System.currentTimeMillis() - Math.round(duration)); + + // Creating return data object + HttpTimerData data = new HttpTimerData(); + + data.setPlatformIdent(platformId); + data.setMethodIdent(registeredMethodId); + data.setSensorTypeIdent(registeredSensorTypeId); + data.setTimeStamp(timestamp); + + data.setDuration(duration); + data.calculateMin(duration); + data.calculateMax(duration); + data.setCpuDuration(cpuDuration); + data.calculateCpuMax(cpuDuration); + data.calculateCpuMin(cpuDuration); + data.setCount(1L); + + // Include additional http information + data.setUri(extractor.getRequestUri(servletRequestClass, httpServletRequest)); + data.setRequestMethod(extractor.getRequestMethod(servletRequestClass, httpServletRequest)); + data.setParameters(extractor.getParameterMap(servletRequestClass, httpServletRequest)); + data.setAttributes(extractor.getAttributes(servletRequestClass, httpServletRequest)); + data.setHeaders(extractor.getHeaders(servletRequestClass, httpServletRequest)); + if (captureSessionData) { + data.setSessionAttributes(extractor.getSessionAttributes(servletRequestClass, httpServletRequest)); + } + + boolean charting = "true".equals(rsc.getSettings().get("charting")); + data.setCharting(charting); + + // returning gathered information + coreService.addMethodSensorData(registeredSensorTypeId, registeredMethodId, null, data); + } catch (IdNotAvailableException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Could not save the timer data because of an unavailable id. " + e.getMessage()); + } + } + } + } + } + } + + /** + * Checks if the given Class is realizing the HttpServletRequest interface directly or + * indirectly. Only if this interface is realized, we can get Http metric information. + * + * @param c + * The class to check + * @return whether or not the HttpServletRequest interface is realized. + */ + private boolean providesHttpMetrics(Class c) { + if (WHITE_LIST.contains(c)) { + return true; + } + if (BLACK_LIST.contains(c)) { + return false; + } + boolean realizesInterface = checkForInterface(c, HTTP_SERVLET_REQUEST_CLASS); + if (realizesInterface) { + WHITE_LIST.addIfAbsent(c); + } else { + BLACK_LIST.addIfAbsent(c); + } + return realizesInterface; + } + + /** + * recursively checks if the given Class object realizes a given interface. This is + * done by recursively checking the implementing interfaces of the current class, then jump to + * the superclass and repeat. If you reach the java.lang.Object class we know that we can stop + * + * @param c + * the Class object to search for + * @param interfaceName + * the name of the interface that should be searched + * @return whether the given class realizes the given interface. + */ + private boolean checkForInterface(Class c, String interfaceName) { + if (c.getName().equals(OBJECT_CLASS)) { + return false; + } + + for (Class clazz : c.getInterfaces()) { + if (clazz.getName().equals(interfaceName)) { + return true; + } + } + + return checkForInterface(c.getSuperclass(), interfaceName); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/http/HttpRequestParameterExtractor.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/http/HttpRequestParameterExtractor.java new file mode 100644 index 000000000..5f1a27a2c --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/http/HttpRequestParameterExtractor.java @@ -0,0 +1,438 @@ +package info.novatec.inspectit.agent.sensor.method.http; + +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.util.StringConstraint; + +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Thread-safe realization to extract information from HttpServletRequests. + * + * @author Stefan Siegl + */ +class HttpRequestParameterExtractor { + + /** + * The logger of this class. Initialized manually. + */ + private static final Logger LOG = LoggerFactory.getLogger(HttpRequestParameterExtractor.class); + + /** + * Constraint for String length. + */ + private StringConstraint strConstraint; + + /** + * Marker method. This method severs for marking the cache key in {@link #methodCache} as + * unavailable. Since we can not put null to a {@link ConcurrentHashMap} we need to + * put some method to serve as a marker. + */ + private Method markerMethod; + + /** + * Keeps track of already looked up Method objects for faster access. Get and Put + * operations are synchronized by the concurrent hash map. + */ + private ConcurrentHashMap methodCache = new ConcurrentHashMap(); + + /** + * Structure to store all necessary methods that we can invoke to get http information. These + * objects are also used to cache the Method object in a cache. + * + * @author Stefan Siegl + */ + private enum HttpMethods { + /** Request URI in Servlet. */ + SERVLET_REQUEST_URI("getRequestURI", (Class[]) null), + /** Parameter map. */ + SERVLET_GET_PARAMETER_MAP("getParameterMap", (Class[]) null), + /** Gets all attributes names. */ + SERVLET_GET_ATTRIBUTE_NAMES("getAttributeNames", (Class[]) null), + /** Gets a given attributes name value. */ + SERVLET_GET_ATTRIBUTE("getAttribute", new Class[] { String.class }), + /** Gets all header names. */ + SERVLET_GET_HEADER_NAMES("getHeaderNames", (Class[]) null), + /** Gets the value of one given header. */ + SERVLET_GET_HEADER("getHeader", new Class[] { String.class }), + /** Gets the session. */ + SERVLET_GET_SESSION("getSession", new Class[] { boolean.class }), + /** Reads the request method. */ + SERVLET_GET_METHOD("getMethod", (Class[]) null), + /** Gets all attribute names in the session. */ + SESSION_GET_ATTRIBUTE_NAMES("getAttributeNames", (Class[]) null), + /** Gets the value of a session attribute. */ + SESSION_GET_ATTRIBUTE("getAttribute", new Class[] { String.class }); + + /** + * Constructor. + * + * @param methodName + * method + * @param parameters + * parameters + */ + private HttpMethods(String methodName, Class[] parameters) { // NOPMD + this.methodName = methodName; + this.parameters = parameters; + } + + /** name of the method. */ + private String methodName; + /** parameters of the methods. */ + private Class[] parameters; + } + + /** + * Constructor. + * + * @param strConstraint + * the string constraints. + */ + public HttpRequestParameterExtractor(StringConstraint strConstraint) { + this.strConstraint = strConstraint; + try { + // setting marker method to point to Object.toString() + // this will represent non existing cache method + markerMethod = Object.class.getMethod("toString", new Class[0]); + } catch (Exception e) { + throw new IllegalStateException("Method toString() can not be found", e); + } + } + + /** + * Reads the request URI from the given HttpServletRequest object and stores it + * with the given HttpTimerData object. + * + * @param httpServletRequestClass + * the Class object representing the class of the given + * HttpServletRequest + * @param httpServletRequest + * the object realizing the HttpServletRequest interface. + * @return the request uri + */ + public String getRequestUri(Class httpServletRequestClass, Object httpServletRequest) { + Method m = retrieveMethod(HttpMethods.SERVLET_REQUEST_URI, httpServletRequestClass); + if (null == m) { + return HttpTimerData.UNDEFINED; + } + + try { + String uri = (String) m.invoke(httpServletRequest, (Object[]) null); + if (null != uri) { + return uri; + } else { + return HttpTimerData.UNDEFINED; + } + } catch (Exception e) { + LOG.error("Invocation on given object failed.", e); + return HttpTimerData.UNDEFINED; + } + } + + /** + * Reads the request URI from the given HttpServletRequest object and stores it + * with the given HttpTimerData object. + * + * @param httpServletRequestClass + * the Class object representing the class of the given + * HttpServletRequest + * @param httpServletRequest + * the object realizing the HttpServletRequest interface. + * @return the request method + */ + public String getRequestMethod(Class httpServletRequestClass, Object httpServletRequest) { + Method m = retrieveMethod(HttpMethods.SERVLET_GET_METHOD, httpServletRequestClass); + if (null == m) { + return HttpTimerData.UNDEFINED; + } + + try { + String requestMethod = (String) m.invoke(httpServletRequest, (Object[]) null); + if (null != requestMethod) { + return requestMethod; + } else { + return HttpTimerData.UNDEFINED; + } + } catch (Exception e) { + LOG.error("Invocation on given object failed.", e); + return HttpTimerData.UNDEFINED; + } + } + + /** + * Reads all request parameters from the given HttpServletRequest object and stores + * them with the given HttpTimerData object. + * + * @param httpServletRequestClass + * the Class object representing the class of the given + * HttpServletRequest + * @param httpServletRequest + * the object realizing the HttpServletRequest interface. + * @return the parameters + */ + public Map getParameterMap(Class httpServletRequestClass, Object httpServletRequest) { + Method m = retrieveMethod(HttpMethods.SERVLET_GET_PARAMETER_MAP, httpServletRequestClass); + if (null == m) { + return null; + } + + try { + @SuppressWarnings("unchecked") + Map parameterMap = (Map) m.invoke(httpServletRequest, (Object[]) null); + + if (null == parameterMap || parameterMap.isEmpty()) { + return null; + } + + return strConstraint.crop(parameterMap); + } catch (Exception e) { + LOG.error("Invocation on given object failed.", e); + return null; + } + } + + /** + * Reads all request attributes from the given HttpServletRequest object and stores + * them with the given HttpTimerData object. + * + * @param httpServletRequestClass + * the Class object representing the class of the given + * HttpServletRequest + * @param httpServletRequest + * the object realizing the HttpServletRequest interface. + * @return the attributes + */ + public Map getAttributes(Class httpServletRequestClass, Object httpServletRequest) { + Method attributesMethod = retrieveMethod(HttpMethods.SERVLET_GET_ATTRIBUTE_NAMES, httpServletRequestClass); + if (null == attributesMethod) { + return null; + } + + Method attributeValue = retrieveMethod(HttpMethods.SERVLET_GET_ATTRIBUTE, httpServletRequestClass); + if (null == attributeValue) { + return null; + } + + try { + @SuppressWarnings("unchecked") + Enumeration params = (Enumeration) attributesMethod.invoke(httpServletRequest, (Object[]) null); + Map attributes = new HashMap(); + if (null == params) { + if (LOG.isDebugEnabled()) { + LOG.debug("Attribute enumeration was "); + } + return null; + } + while (params.hasMoreElements()) { + String attrName = params.nextElement(); + Object value = attributeValue.invoke(httpServletRequest, new Object[] { attrName }); + attributes.put(attrName, strConstraint.crop(getAttributeValue(value))); + } + return attributes; + } catch (Exception e) { + LOG.error("Invocation of " + attributesMethod.getName() + " to get attributes on given object failed.", e); + return null; + } + } + + /** + * Reads all headers from the given HttpServletRequest object and stores them with + * the given HttpTimerData object. + * + * @param httpServletRequestClass + * the Class object representing the class of the given + * HttpServletRequest + * @param httpServletRequest + * the object realizing the HttpServletRequest interface. + * @return the headers + */ + public Map getHeaders(Class httpServletRequestClass, Object httpServletRequest) { + Method headerNamesMethod = retrieveMethod(HttpMethods.SERVLET_GET_HEADER_NAMES, httpServletRequestClass); + if (null == headerNamesMethod) { + return null; + } + + Method headerValueMethod = retrieveMethod(HttpMethods.SERVLET_GET_HEADER, httpServletRequestClass); + if (null == headerValueMethod) { + return null; + } + + try { + @SuppressWarnings("unchecked") + Enumeration headers = (Enumeration) headerNamesMethod.invoke(httpServletRequest, (Object[]) null); + Map headersResult = new HashMap(); + if (headers != null) { + while (headers.hasMoreElements()) { + String headerName = (String) headers.nextElement(); + String headerValue = (String) headerValueMethod.invoke(httpServletRequest, new Object[] { headerName }); + headersResult.put(headerName, strConstraint.crop(headerValue)); + } + return headersResult; + } + } catch (Exception e) { + LOG.error("Invocation of to get attributes on given object failed.", e); + } + return null; + } + + /** + * Reads all session attributes from the HttpSession of the given + * HttpServletRequest object and stores them with the given + * HttpTimerData object. This method ensures that no new session will be created. + * + * @param httpServletRequestClass + * the Class object representing the class of the given + * HttpServletRequest + * @param httpServletRequest + * the object realizing the HttpServletRequest interface. + * @return session attributes + */ + public Map getSessionAttributes(Class httpServletRequestClass, Object httpServletRequest) { + Method getSessionMethod = retrieveMethod(HttpMethods.SERVLET_GET_SESSION, httpServletRequestClass); + + if (null == getSessionMethod) { // Could not retrieve method + return null; + } + + Object httpSession; + Class httpSessionClass; + try { + httpSession = getSessionMethod.invoke(httpServletRequest, new Object[] { Boolean.FALSE }); + if (httpSession == null) { + // Currently we do not have a session and thus cannot get any session attributes + if (LOG.isDebugEnabled()) { + LOG.debug("No session can be found"); + } + return null; + } + httpSessionClass = httpSession.getClass(); + + } catch (Exception e) { + LOG.error("Invocation of to get attributes on given object failed.", e); + // we cannot go on! + return null; + } + + Method getAttributeNamesSession = retrieveMethod(HttpMethods.SESSION_GET_ATTRIBUTE_NAMES, httpSessionClass); + if (null == getAttributeNamesSession) { + return null; + } + + Method getAttributeValueSession = retrieveMethod(HttpMethods.SESSION_GET_ATTRIBUTE, httpSessionClass); + if (null == getAttributeValueSession) { + return null; + } + + try { + @SuppressWarnings("unchecked") + Enumeration sessionAttr = (Enumeration) getAttributeNamesSession.invoke(httpSession, (Object[]) null); + Map sessionAttributes = new HashMap(); + + if (null != sessionAttr) { + while (sessionAttr.hasMoreElements()) { + String sessionAtt = sessionAttr.nextElement(); + Object sessionValue = (Object) getAttributeValueSession.invoke(httpSession, sessionAtt); + sessionAttributes.put(sessionAtt, strConstraint.crop(getAttributeValue(sessionValue))); + } + return sessionAttributes; + } + } catch (Exception e) { + LOG.error("Invocation of to get attributes on given object failed.", e); + } + return null; + } + + /** + * Tries a lookup in the cache first, then tries to get the Method object via + * reflection. + * + * @param httpMethod + * the Method to lookup + * @param clazzUsedToLookup + * the class to use if reflection lookup is necessary (if it is not already in the + * cache) + * @return the Method object or null if the method cannot be found. + */ + private Method retrieveMethod(HttpMethods httpMethod, Class clazzUsedToLookup) { + String cacheLookupName = getCacheLookupName(httpMethod, clazzUsedToLookup); + Method m = methodCache.get(cacheLookupName); + + if (null == m) { + // We do not yet have the method in the Cache + try { + m = clazzUsedToLookup.getMethod(httpMethod.methodName, httpMethod.parameters); + m.setAccessible(true); + Method existing = methodCache.putIfAbsent(cacheLookupName, m); + if (null != existing) { + m = existing; + } + } catch (Exception e) { + LOG.error("The provided class " + clazzUsedToLookup.getCanonicalName() + " did not provide the desired method.", e); + + // Do not try to look up every time. + // Can not place null as value anyway + methodCache.putIfAbsent(cacheLookupName, markerMethod); + } + } else if (markerMethod.equals(m)) { + return null; + } + + return m; + } + + /** + * Generates and return a lookup name for the cache. + * + * @param httpMethod + * the Method to lookup + * @param clazz + * the concrete class to lookup the method upon. + * @return the generated lookup name. + */ + private String getCacheLookupName(HttpMethods httpMethod, Class clazz) { + return clazz.getCanonicalName() + '#' + httpMethod.methodName; + } + + /** + * Utility method that checks if the attribute provided is an Array, and if it so, formats the + * return String in the human-readable form. If the attribute is not an Array, the + * {@link Object#toString()} will be returned. If attribute is null, then + * '' will be returned. + * + * @param attribute + * Attribute to get {@link String} value for. + * @return Human-readable value of attribute. + */ + private String getAttributeValue(Object attribute) { + if (null == attribute) { + return ""; + } + if (attribute.getClass().isArray()) { + StringBuilder stringBuilder = new StringBuilder("["); + boolean isFirst = true; + + int length = Array.getLength(attribute); + for (int i = 0; i < length; i++) { + if (isFirst) { + stringBuilder.append(Array.get(attribute, i)); + isFirst = false; + } else { + stringBuilder.append(", "); + stringBuilder.append(Array.get(attribute, i)); + } + } + stringBuilder.append(']'); + return stringBuilder.toString(); + } else { + return attribute.toString(); + } + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/http/HttpSensor.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/http/HttpSensor.java new file mode 100644 index 000000000..a5b169ac3 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/http/HttpSensor.java @@ -0,0 +1,70 @@ +package info.novatec.inspectit.agent.sensor.method.http; + +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.hooking.IHook; +import info.novatec.inspectit.agent.sensor.method.AbstractMethodSensor; +import info.novatec.inspectit.agent.sensor.method.IMethodSensor; +import info.novatec.inspectit.util.Timer; + +import java.lang.management.ManagementFactory; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; + +/** + * The http sensor which initializes and returns the {@link HttpHook} class. + * + * @author Stefan Siegl + */ +public class HttpSensor extends AbstractMethodSensor implements IMethodSensor { + + /** + * The hook. + */ + private HttpHook hook = null; + + /** + * The timer used for accurate measuring. + */ + @Autowired + private Timer timer; + + /** + * The ID manager. + */ + @Autowired + private IIdManager idManager; + + /** + * No-arg constructor needed for Spring. + */ + public HttpSensor() { + } + + /** + * Constructor. + * + * @param timer + * the timer. + * @param idManager + * the idmanager. + */ + public HttpSensor(Timer timer, IIdManager idManager) { + this.timer = timer; + this.idManager = idManager; + } + + /** + * {@inheritDoc} + */ + public void init(Map parameters) { + hook = new HttpHook(timer, idManager, parameters, ManagementFactory.getThreadMXBean()); + } + + /** + * {@inheritDoc} + */ + public IHook getHook() { + return hook; + } +} \ No newline at end of file diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/http/StartEndMarker.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/http/StartEndMarker.java new file mode 100644 index 000000000..19ac899e5 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/http/StartEndMarker.java @@ -0,0 +1,111 @@ +package info.novatec.inspectit.agent.sensor.method.http; + +import info.novatec.inspectit.agent.sensor.method.http.StartEndMarker.MutableInteger; + +/** + * Provides a thread local means to store the information if we returned to the end of a given + * method an invocation. + * + * @author Stefan Siegl + */ +public class StartEndMarker extends ThreadLocal { + + /** + * Increase the counter. Use this at the point that you want marked. + */ + public void markCall() { + super.get().increase(); + } + + /** + * Increase the counter. + */ + public void markEndCall() { + super.get().decrease(); + } + + /** + * checks if we already returned to the method that marked the first call. + * + * @return checks if we already returned to the method that marked the first call. + */ + public boolean matchesFirst() { + return super.get().getValue() == 0; + } + + /** + * Checks if we already marked a call. + * + * @return Checks if we already marked a call + */ + public boolean isMarkerSet() { + return super.get().isSet(); + } + + /** + * {@inheritDoc} + */ + @Override + protected MutableInteger initialValue() { + return new MutableInteger(); + } + + /** + * Simple realization of a mutable integer object. We cannot use the one of the commons project + * here as we will have a new dependency. + * + * @author Stefan Siegl + */ + static class MutableInteger { + /** The wrapped integer. */ + private int value; + + /** + * Marks the first call to the counter. This is necessary in the cases where we have a non + * http providing method at first (for this case, lets say no other method has the http + * sensor). This means that the second after body would be reached with counter value 0, + * which would return true for the check if the counter is returned to its starting + * position. + */ + private boolean set = false; + + /** Constructor. */ + public MutableInteger() { + value = 0; + } + + /** + * Returns the value. + * + * @return the value. + */ + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + + /** + * Increases the value and sets the marker to be accessed. + */ + public void increase() { + value++; + if (!set) { + set = true; + } + } + + /** + * Decreases the value and sets the marker to be accessed. + */ + public void decrease() { + value--; + } + + public boolean isSet() { + return set; + } + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/invocationsequence/InvocationSequenceHook.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/invocationsequence/InvocationSequenceHook.java new file mode 100644 index 000000000..fd48a85f5 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/invocationsequence/InvocationSequenceHook.java @@ -0,0 +1,610 @@ +package info.novatec.inspectit.agent.sensor.method.invocationsequence; + +import info.novatec.inspectit.agent.buffer.IBufferStrategy; +import info.novatec.inspectit.agent.config.IPropertyAccessor; +import info.novatec.inspectit.agent.config.impl.MethodSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.PlatformSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IObjectStorage; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.core.ListListener; +import info.novatec.inspectit.agent.hooking.IConstructorHook; +import info.novatec.inspectit.agent.hooking.IMethodHook; +import info.novatec.inspectit.agent.sending.ISendingStrategy; +import info.novatec.inspectit.agent.sensor.exception.ExceptionSensor; +import info.novatec.inspectit.agent.sensor.method.jdbc.ConnectionMetaDataSensor; +import info.novatec.inspectit.agent.sensor.method.jdbc.ConnectionSensor; +import info.novatec.inspectit.agent.sensor.method.jdbc.PreparedStatementParameterSensor; +import info.novatec.inspectit.agent.sensor.method.jdbc.PreparedStatementSensor; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.communication.SystemSensorData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.ParameterContentData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.util.StringConstraint; +import info.novatec.inspectit.util.ThreadLocalStack; +import info.novatec.inspectit.util.Timer; + +import java.net.ConnectException; +import java.sql.Timestamp; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections.CollectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The invocation sequence hook stores the record of the invocation sequences in a + * {@link ThreadLocal} object. + *

+ * This hook implements the {@link ICoreService} interface which simulates the core service to all + * other hooks which are called during the execution of this invocation. The + * defaultCoreService field is used to delegate some calls directly to the original + * core service and later sending of the data to the server. + * + * @author Patrice Bouillet + * + */ +public class InvocationSequenceHook implements IMethodHook, IConstructorHook, ICoreService { + + /** + * The logger of this class. Initialized manually. + */ + private static final Logger LOG = LoggerFactory.getLogger(InvocationSequenceHook.class); + + /** + * The ID manager. + */ + private final IIdManager idManager; + + /** + * The property accessor. + */ + private final IPropertyAccessor propertyAccessor; + + /** + * The {@link ThreadLocal} object which holds an {@link InvocationSequenceData} object if an + * invocation record is started. + */ + private final ThreadLocal threadLocalInvocationData = new ThreadLocal(); + + /** + * Stores the value of the method ID in the {@link ThreadLocal} object. Used to identify the + * correct start and end of the record. + */ + private final ThreadLocal invocationStartId = new ThreadLocal(); + + /** + * Stores the count of the of the starting method being called in the same invocation sequence + * so that closing is done on the right end. + */ + private final ThreadLocal invocationStartIdCount = new ThreadLocal(); + + /** + * The timer used for accurate measuring. + */ + private final Timer timer; + + /** + * The stack containing the start time values. + */ + private final ThreadLocalStack timeStack = new ThreadLocalStack(); + + /** + * Saves the min duration for faster access of the values. + */ + private Map minDurationMap = new HashMap(); + + /** + * The StringConstraint to ensure a maximum length of strings. + */ + private StringConstraint strConstraint; + + /** + * If enhanced exception sensor is ON. + */ + private final boolean enhancedExceptionSensor; + + /** + * The default constructor is initialized with a reference to the original {@link ICoreService} + * implementation to delegate all calls to if the data needs to be sent. + * + * @param timer + * The timer. + * @param idManager + * The ID manager. + * @param propertyAccessor + * The property accessor. + * @param param + * Additional parameters. + * @param enhancedExceptionSensor + * If enhanced exception sensor is ON. + */ + public InvocationSequenceHook(Timer timer, IIdManager idManager, IPropertyAccessor propertyAccessor, Map param, boolean enhancedExceptionSensor) { + this.timer = timer; + this.idManager = idManager; + this.propertyAccessor = propertyAccessor; + this.strConstraint = new StringConstraint(param); + this.enhancedExceptionSensor = enhancedExceptionSensor; + } + + /** + * {@inheritDoc} + */ + public void beforeBody(long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc) { + if (skip(rsc)) { + return; + } + + try { + long platformId = idManager.getPlatformId(); + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + long registeredMethodId = idManager.getRegisteredMethodId(methodId); + + if (null == threadLocalInvocationData.get()) { + // save the start time + timeStack.push(new Double(timer.getCurrentTime())); + + // the sensor type is only available in the beginning of the + // sequence trace + long registeredSensorTypeId = idManager.getRegisteredSensorTypeId(sensorTypeId); + + // no invocation tracer is currently started, so we do that now. + InvocationSequenceData invocationSequenceData = new InvocationSequenceData(timestamp, platformId, registeredSensorTypeId, registeredMethodId); + threadLocalInvocationData.set(invocationSequenceData); + + invocationStartId.set(Long.valueOf(methodId)); + invocationStartIdCount.set(Long.valueOf(1)); + } else { + if (methodId == invocationStartId.get().longValue()) { + long count = invocationStartIdCount.get().longValue(); + invocationStartIdCount.set(Long.valueOf(count + 1)); + } + // A subsequent call to the before body method where an + // invocation tracer is already started. + InvocationSequenceData invocationSequenceData = threadLocalInvocationData.get(); + invocationSequenceData.setChildCount(invocationSequenceData.getChildCount() + 1L); + + InvocationSequenceData nestedInvocationSequenceData = new InvocationSequenceData(timestamp, platformId, invocationSequenceData.getSensorTypeIdent(), registeredMethodId); + nestedInvocationSequenceData.setStart(timer.getCurrentTime()); + nestedInvocationSequenceData.setParentSequence(invocationSequenceData); + + invocationSequenceData.getNestedSequences().add(nestedInvocationSequenceData); + + threadLocalInvocationData.set(nestedInvocationSequenceData); + } + } catch (IdNotAvailableException idNotAvailableException) { + if (LOG.isDebugEnabled()) { + LOG.debug("Could not start invocation sequence because of a (currently) not mapped ID"); + } + } + } + + /** + * {@inheritDoc} + */ + public void firstAfterBody(long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) { + if (skip(rsc)) { + return; + } + + InvocationSequenceData invocationSequenceData = threadLocalInvocationData.get(); + + if (null != invocationSequenceData) { + if (methodId == invocationStartId.get().longValue()) { + long count = invocationStartIdCount.get().longValue(); + invocationStartIdCount.set(Long.valueOf(count - 1)); + + if (0 == count - 1) { + timeStack.push(new Double(timer.getCurrentTime())); + } + } + } + } + + /** + * {@inheritDoc} + */ + public void secondAfterBody(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) { + if (skip(rsc)) { + return; + } + + InvocationSequenceData invocationSequenceData = threadLocalInvocationData.get(); + + if (null != invocationSequenceData) { + // check if some properties need to be accessed and saved + if (rsc.isPropertyAccess()) { + List parameterContentData = propertyAccessor.getParameterContentData(rsc.getPropertyAccessorList(), object, parameters, result); + + // crop the content strings of all ParameterContentData + for (ParameterContentData contentData : parameterContentData) { + contentData.setContent(strConstraint.crop(contentData.getContent())); + } + } + + if (methodId == invocationStartId.get().longValue() && 0 == invocationStartIdCount.get().longValue()) { + double endTime = timeStack.pop().doubleValue(); + double startTime = timeStack.pop().doubleValue(); + double duration = endTime - startTime; + + // complete the sequence and store the data object in the 'true' + // core service so that it can be transmitted to the server. we + // just need an arbitrary prefix so that this sequence will + // never be overwritten in the core service! + if (minDurationMap.containsKey(invocationStartId.get())) { + checkForSavingOrNot(coreService, methodId, sensorTypeId, rsc, invocationSequenceData, startTime, endTime, duration); + } else { + // maybe not saved yet in the map + if (rsc.getSettings().containsKey("minduration")) { + minDurationMap.put(invocationStartId.get(), Double.valueOf((String) rsc.getSettings().get("minduration"))); + checkForSavingOrNot(coreService, methodId, sensorTypeId, rsc, invocationSequenceData, startTime, endTime, duration); + } else { + invocationSequenceData.setDuration(duration); + invocationSequenceData.setStart(startTime); + invocationSequenceData.setEnd(endTime); + coreService.addMethodSensorData(sensorTypeId, methodId, String.valueOf(System.currentTimeMillis()), invocationSequenceData); + } + } + + threadLocalInvocationData.set(null); + } else { + // just close the nested sequence and set the correct child count + InvocationSequenceData parentSequence = invocationSequenceData.getParentSequence(); + // check if we should not include this invocation because of exception delegation or + // SQL wrapping + if (removeDueToExceptionDelegation(rsc, invocationSequenceData) || removeDueToWrappedSqls(rsc, invocationSequenceData)) { + parentSequence.getNestedSequences().remove(invocationSequenceData); + parentSequence.setChildCount(parentSequence.getChildCount() - 1); + // but connect all possible children to the parent then + // we are eliminating one level here + if (CollectionUtils.isNotEmpty(invocationSequenceData.getNestedSequences())) { + parentSequence.getNestedSequences().addAll(invocationSequenceData.getNestedSequences()); + parentSequence.setChildCount(parentSequence.getChildCount() + invocationSequenceData.getChildCount()); + } + } else { + invocationSequenceData.setEnd(timer.getCurrentTime()); + invocationSequenceData.setDuration(invocationSequenceData.getEnd() - invocationSequenceData.getStart()); + parentSequence.setChildCount(parentSequence.getChildCount() + invocationSequenceData.getChildCount()); + } + threadLocalInvocationData.set(parentSequence); + } + } + } + + /** + * Returns if the given {@link InvocationSequenceData} should be removed due to the exception + * constructor delegation. + * + * @param rsc + * {@link RegisteredSensorConfig} + * @param invocationSequenceData + * {@link InvocationSequenceData} to check. + * @return True if the invocation should be removed. + */ + private boolean removeDueToExceptionDelegation(RegisteredSensorConfig rsc, InvocationSequenceData invocationSequenceData) { + if (1 == rsc.getSensorTypeConfigs().size()) { + MethodSensorTypeConfig methodSensorTypeConfig = rsc.getSensorTypeConfigs().get(0); + + if (ExceptionSensor.class.getCanonicalName().equals(methodSensorTypeConfig.getClassName())) { + return CollectionUtils.isEmpty(invocationSequenceData.getExceptionSensorDataObjects()); + } + } + + return false; + } + + /** + * Returns if the given {@link InvocationSequenceData} should be removed due to the wrapping of + * the prepared SQL statements. + * + * @param rsc + * {@link RegisteredSensorConfig} + * @param invocationSequenceData + * {@link InvocationSequenceData} to check. + * @return True if the invocation should be removed. + */ + private boolean removeDueToWrappedSqls(RegisteredSensorConfig rsc, InvocationSequenceData invocationSequenceData) { + if (1 == rsc.getSensorTypeConfigs().size() || (2 == rsc.getSensorTypeConfigs().size() && enhancedExceptionSensor)) { + for (MethodSensorTypeConfig methodSensorTypeConfig : rsc.getSensorTypeConfigs()) { + + if (PreparedStatementSensor.class.getCanonicalName().equals(methodSensorTypeConfig.getClassName())) { + if (null == invocationSequenceData.getSqlStatementData() || 0 == invocationSequenceData.getSqlStatementData().getCount()) { + return true; + } + } + } + } + + return false; + } + + /** + * Defines if the invocation container should skip the creation and processing of the invocation + * for the given object and {@link RegisteredSensorConfig}. We will skip if any of following + * conditions are met: + *

    + *
  • {@link RegisteredSensorConfig} has only exception sensor and object class does not match + * the target class name. + *
  • {@link RegisteredSensorConfig} has only prepared statement parameter sensor. + *
  • {@link RegisteredSensorConfig} has only connection sensor. + *
  • {@link RegisteredSensorConfig} has only connection meta data sensor. + *
+ * + * @param rsc + * {@link RegisteredSensorConfig}. + * + * @return Return true if hook should skip creation and processing, false + * otherwise. + */ + private boolean skip(RegisteredSensorConfig rsc) { + if (1 == rsc.getSensorTypeConfigs().size() || (2 == rsc.getSensorTypeConfigs().size() && enhancedExceptionSensor)) { + + for (MethodSensorTypeConfig methodSensorTypeConfig : rsc.getSensorTypeConfigs()) { + if (PreparedStatementParameterSensor.class.getCanonicalName().equals(methodSensorTypeConfig.getClassName())) { + return true; + } + + if (ConnectionSensor.class.getCanonicalName().equals(methodSensorTypeConfig.getClassName())) { + return true; + } + + if (ConnectionMetaDataSensor.class.getCanonicalName().equals(methodSensorTypeConfig.getClassName())) { + return true; + } + } + } + return false; + } + + /** + * This checks if the invocation has to be saved or not (like the min duration is set and the + * invocation is faster than the specified time). + * + * @param coreService + * The reference to the core service which holds the data objects etc. + * @param methodId + * The unique method id. + * @param sensorTypeId + * The unique sensor type id. + * @param rsc + * The {@link RegisteredSensorConfig} object which holds all the information of the + * executed method. + * @param invocationSequenceData + * The invocation sequence data object. + * @param startTime + * The start time. + * @param endTime + * The end time. + * @param duration + * The actual duration. + */ + private void checkForSavingOrNot(ICoreService coreService, long methodId, long sensorTypeId, RegisteredSensorConfig rsc, InvocationSequenceData invocationSequenceData, double startTime, // NOCHK + double endTime, double duration) { + double minduration = minDurationMap.get(invocationStartId.get()).doubleValue(); + if (duration >= minduration) { + if (LOG.isDebugEnabled()) { + LOG.debug("Saving invocation. " + duration + " > " + minduration + " ID(local): " + rsc.getId()); + } + invocationSequenceData.setDuration(duration); + invocationSequenceData.setStart(startTime); + invocationSequenceData.setEnd(endTime); + coreService.addMethodSensorData(sensorTypeId, methodId, String.valueOf(System.currentTimeMillis()), invocationSequenceData); + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("Not saving invocation. " + duration + " < " + minduration + " ID(local): " + rsc.getId()); + } + } + } + + /** + * {@inheritDoc} + */ + public void beforeConstructor(long methodId, long sensorTypeId, Object[] parameters, RegisteredSensorConfig rsc) { + beforeBody(methodId, sensorTypeId, null, parameters, rsc); + } + + /** + * {@inheritDoc} + */ + public void afterConstructor(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc) { + firstAfterBody(methodId, sensorTypeId, object, parameters, null, rsc); + secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, null, rsc); + } + + /** + * Save the data objects which are coming from all the different sensor types in the current + * invocation tracer context. + * + * @param dataObject + * The data object to save. + */ + private void saveDataObject(DefaultData dataObject) { + InvocationSequenceData invocationSequenceData = threadLocalInvocationData.get(); + + if (dataObject.getClass().equals(SqlStatementData.class)) { + // don't overwrite an already existing sql statement data object. + if (null == invocationSequenceData.getSqlStatementData()) { + invocationSequenceData.setSqlStatementData((SqlStatementData) dataObject); + } + } + + if (dataObject.getClass().equals(HttpTimerData.class)) { + // don't overwrite ourself but overwrite timers + if (null == invocationSequenceData.getTimerData() || invocationSequenceData.getTimerData().getClass().equals(TimerData.class)) { + invocationSequenceData.setTimerData((HttpTimerData) dataObject); + } + } + + if (dataObject.getClass().equals(TimerData.class)) { + // don't overwrite an already existing timerdata or httptimerdata object. + if (null == invocationSequenceData.getTimerData()) { + invocationSequenceData.setTimerData((TimerData) dataObject); + } + } + + if (dataObject.getClass().equals(ExceptionSensorData.class)) { + ExceptionSensorData exceptionSensorData = (ExceptionSensorData) dataObject; + invocationSequenceData.addExceptionSensorData(exceptionSensorData); + } + } + + // ////////////////////////////////////////////// + // All methods from the ICoreService are below // + // ////////////////////////////////////////////// + + /** + * {@inheritDoc} + */ + public void addMethodSensorData(long sensorTypeId, long methodId, String prefix, MethodSensorData methodSensorData) { + if (null == threadLocalInvocationData.get()) { + LOG.error("thread data NULL!!!!"); + return; + } + saveDataObject(methodSensorData.finalizeData()); + } + + /** + * {@inheritDoc} + */ + public void addObjectStorage(long sensorTypeId, long methodId, String prefix, IObjectStorage objectStorage) { + if (null == threadLocalInvocationData.get()) { + LOG.error("thread data NULL!!!!"); + return; + } + DefaultData defaultData = objectStorage.finalizeDataObject(); + saveDataObject(defaultData.finalizeData()); + } + + /** + * {@inheritDoc} + */ + public void addPlatformSensorData(long sensorTypeIdent, SystemSensorData systemSensorData) { + saveDataObject(systemSensorData.finalizeData()); + } + + /** + * {@inheritDoc} + */ + public void addExceptionSensorData(long sensorTypeIdent, long throwableIdentityHashCode, ExceptionSensorData exceptionSensorData) { + if (null == threadLocalInvocationData.get()) { + LOG.info("thread data NULL!!!!"); + return; + } + saveDataObject(exceptionSensorData.finalizeData()); + } + + // ///////////////////////////////////////////////// // + // Return NULL because no saved data can be returned // + // ///////////////////////////////////////////////// // + + /** + * {@inheritDoc} + */ + public ExceptionSensorData getExceptionSensorData(long sensorTypeIdent, long throwableIdentityHashCode) { + return null; + } + + /** + * {@inheritDoc} + */ + public MethodSensorData getMethodSensorData(long sensorTypeIdent, long methodIdent, String prefix) { + return null; + } + + /** + * {@inheritDoc} + */ + public IObjectStorage getObjectStorage(long sensorTypeIdent, long methodIdent, String prefix) { + return null; + } + + /** + * {@inheritDoc} + */ + public SystemSensorData getPlatformSensorData(long sensorTypeIdent) { + return null; + } + + // ////////////////////////////////////////////// + // All unsupported methods are below from here // + // ////////////////////////////////////////////// + + /** + * {@inheritDoc} + */ + public void addListListener(ListListener listener) { + throw new UnsupportedMethodException(); + } + + /** + * {@inheritDoc} + */ + public void addSendStrategy(ISendingStrategy strategy) { + throw new UnsupportedMethodException(); + } + + /** + * {@inheritDoc} + */ + public void connect() throws ConnectException { + throw new UnsupportedMethodException(); + } + + /** + * {@inheritDoc} + */ + public void removeListListener(ListListener listener) { + throw new UnsupportedMethodException(); + } + + /** + * {@inheritDoc} + */ + public void sendData() { + throw new UnsupportedMethodException(); + } + + /** + * {@inheritDoc} + */ + public void setBufferStrategy(IBufferStrategy bufferStrategy) { + throw new UnsupportedMethodException(); + } + + /** + * {@inheritDoc} + */ + public void startSendingStrategies() { + throw new UnsupportedMethodException(); + } + + /** + * {@inheritDoc} + */ + public void addPlatformSensorType(PlatformSensorTypeConfig platformSensorTypeConfig) { + throw new UnsupportedMethodException(); + } + + /** + * {@inheritDoc} + */ + public void start() { + throw new UnsupportedMethodException(); + } + + /** + * {@inheritDoc} + */ + public void stop() { + throw new UnsupportedMethodException(); + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/invocationsequence/InvocationSequenceSensor.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/invocationsequence/InvocationSequenceSensor.java new file mode 100644 index 000000000..308c14a2b --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/invocationsequence/InvocationSequenceSensor.java @@ -0,0 +1,92 @@ +package info.novatec.inspectit.agent.sensor.method.invocationsequence; + +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.config.IPropertyAccessor; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.hooking.IHook; +import info.novatec.inspectit.agent.sensor.method.AbstractMethodSensor; +import info.novatec.inspectit.agent.sensor.method.IMethodSensor; +import info.novatec.inspectit.util.Timer; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; + +/** + * The invocation sequence sensor which initializes and returns the {@link InvocationSequenceHook} + * class. + * + * @author Patrice Bouillet + * + */ +public class InvocationSequenceSensor extends AbstractMethodSensor implements IMethodSensor { + + /** + * The timer used for accurate measuring. + */ + @Autowired + private Timer timer; + + /** + * The ID manager. + */ + @Autowired + private IIdManager idManager; + + /** + * The property accessor. + */ + @Autowired + private IPropertyAccessor propertyAccessor; + + /** + * Configuration storage for checking if enhanced exception sensor is ON. + */ + @Autowired + private IConfigurationStorage configurationStorage; + + /** + * The invocation sequence hook. + */ + private InvocationSequenceHook invocationSequenceHook = null; + + /** + * No-arg constructor needed for Spring. + */ + public InvocationSequenceSensor() { + } + + /** + * The default constructor which needs 2 parameter for initialization. + * + * @param timer + * The timer used for accurate measuring. + * @param idManager + * The ID manager. + * @param propertyAccessor + * The property accessor. + * @param configurationStorage + * {@link IConfigurationStorage}. + */ + public InvocationSequenceSensor(Timer timer, IIdManager idManager, IPropertyAccessor propertyAccessor, IConfigurationStorage configurationStorage) { + this.timer = timer; + this.idManager = idManager; + this.propertyAccessor = propertyAccessor; + this.configurationStorage = configurationStorage; + } + + /** + * {@inheritDoc} + */ + public IHook getHook() { + return invocationSequenceHook; + } + + /** + * {@inheritDoc} + */ + public void init(Map parameter) { + invocationSequenceHook = new InvocationSequenceHook(timer, idManager, propertyAccessor, parameter, configurationStorage.isEnhancedExceptionSensorActivated()); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/invocationsequence/UnsupportedMethodException.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/invocationsequence/UnsupportedMethodException.java new file mode 100644 index 000000000..8d39e8254 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/invocationsequence/UnsupportedMethodException.java @@ -0,0 +1,20 @@ +package info.novatec.inspectit.agent.sensor.method.invocationsequence; + +import info.novatec.inspectit.agent.core.ICoreService; + +/** + * Exception used in the {@link InvocationSequenceHook} to mark the methods from the + * {@link ICoreService} which should never be called if the invocation sequence hook mimics the real + * core service implementation. + * + * @author Patrice Bouillet + * + */ +public class UnsupportedMethodException extends RuntimeException { + + /** + * The serial version UID. + */ + private static final long serialVersionUID = 6187250477502855273L; + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionHook.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionHook.java new file mode 100644 index 000000000..6c28b37d1 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionHook.java @@ -0,0 +1,53 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.hooking.IMethodHook; + +/** + * This hook records the creation of statements so that they can be later retrieved by other hooks + * to create valid data objects with query statements. + * + * @author Patrice Bouillet + * + */ +public class ConnectionHook implements IMethodHook { + + /** + * The statement storage to add the statements to. + */ + private StatementStorage statementStorage; + + /** + * Default constructor which needs a reference to the statement storage. + * + * @param statementStorage + * The statement storage. + */ + public ConnectionHook(StatementStorage statementStorage) { + this.statementStorage = statementStorage; + } + + /** + * {@inheritDoc} + */ + public void beforeBody(long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc) { + String sql = (String) parameters[0]; + statementStorage.addSql(sql); + } + + /** + * {@inheritDoc} + */ + public void firstAfterBody(long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) { + // nothing to do + } + + /** + * {@inheritDoc} + */ + public void secondAfterBody(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) { + statementStorage.removeSql(); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataHook.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataHook.java new file mode 100644 index 000000000..17ea00f73 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataHook.java @@ -0,0 +1,44 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.hooking.IConstructorHook; + +/** + * This hook records the meta data information of a connection and stores this into a map for later + * retrieval. + * + * @author Stefan Siegl + * + */ +public class ConnectionMetaDataHook implements IConstructorHook { + + /** + * Storage for connection meta data. + */ + private final ConnectionMetaDataStorage connectionMetaDataStorage; + + /** + * The only constructor which needs the {@link ConnectionMetaDataStorage}. + * + * @param connectionMetaDataStorage + * the connection storage. + */ + public ConnectionMetaDataHook(ConnectionMetaDataStorage connectionMetaDataStorage) { + this.connectionMetaDataStorage = connectionMetaDataStorage; + } + + /** + * {@inheritDoc} + */ + public void afterConstructor(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc) { + connectionMetaDataStorage.add(object); + } + + /** + * {@inheritDoc} + */ + public void beforeConstructor(long methodId, long sensorTypeId, Object[] parameters, RegisteredSensorConfig rsc) { + // nothing + } +} \ No newline at end of file diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataSensor.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataSensor.java new file mode 100644 index 000000000..d51757980 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataSensor.java @@ -0,0 +1,43 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import info.novatec.inspectit.agent.hooking.IHook; +import info.novatec.inspectit.agent.sensor.method.AbstractMethodSensor; +import info.novatec.inspectit.agent.sensor.method.IMethodSensor; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; + +/** + * This sensor reads the meta information from a connection. + * + * @author Stefan Siegl + */ +public class ConnectionMetaDataSensor extends AbstractMethodSensor implements IMethodSensor { + + /** + * Meta information about the connection. + */ + @Autowired + private ConnectionMetaDataStorage connectionStorage; + + /** + * The used prepared statement hook. + */ + private ConnectionMetaDataHook connectionHook = null; + + /** + * {@inheritDoc} + */ + public IHook getHook() { + return connectionHook; + } + + /** + * {@inheritDoc} + */ + public void init(Map parameter) { + connectionHook = new ConnectionMetaDataHook(connectionStorage); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataStorage.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataStorage.java new file mode 100644 index 000000000..9b95feff7 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataStorage.java @@ -0,0 +1,269 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.util.ReflectionCache; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +/** + * Storage for the meta information of JDBC connection classes. + * + * @author Stefan Siegl + */ +@Component +public class ConnectionMetaDataStorage { + + /** + * This cache keeps track of the meta information for connection objects. The key is the + * Connection object. As connections are re-used it makes sense to keep the meta + * information cached, as to not request this information over and over again. + * + * Also we use weak keys as to allow the garbage collector to remove the entries as soon as + * nobody else holds references to the connection (if this happens, the connection itself is + * garbage collected and thus we need not hold meta information anymore). + * + * Also note that having weakKeys tells the Cache to use identity comparison (==) instead of + * equals() comparison. This is just the thing we want as we can ensure that connections + * identity stays that same. On top of that == is faster than equals. + * + * Note that this data structure provides atomic access like a ConcurrentMap. + * . + * + * Package access for easier testing. + */ + Cache storage = CacheBuilder.newBuilder().weakKeys().softValues().build(); + + /** + * Extractor to read data from the connection instance. + * + * Package access for easier testing. + */ + ConnectionMetaDataExtractor dataExtractor = new ConnectionMetaDataExtractor(); + + /** + * Retrieves the ConnectionMetaData stored with this connection. + * + * @param connection + * the connection instance + * @return the ConnectionMetaData stored with this connection. + */ + protected ConnectionMetaData get(Object connection) { + if (null == connection) { + return null; + } + return storage.getIfPresent(connection); + } + + /** + * Populates the given SQL Statement data with the meta information from the storage if this + * data exist. + * + * @param sqlData + * the data object to populate. + * @param connection + * the connection. + */ + public void populate(SqlStatementData sqlData, Object connection) { + ConnectionMetaData connectionMetaData = get(connection); + if (null != connectionMetaData) { + sqlData.setDatabaseProductName(connectionMetaData.product); + sqlData.setDatabaseProductVersion(connectionMetaData.version); + sqlData.setDatabaseUrl(connectionMetaData.url); + } + } + + /** + * Adds the given connection to the storage. + * + * @param connection + * the connection instance + */ + public void add(Object connection) { + if (null == connection) { + return; + } + ConnectionMetaData data = get(connection); + if (null != data) { + return; // already in the cache. + } + + data = dataExtractor.parse(connection); + + storage.put(connection, data); + } + + /** + * Value holder for meta information of connection instances. + * + * @author Stefan Siegl + */ + public static class ConnectionMetaData { + /** The connection URL. */ + public String url; // NOCHK + /** The product name of the database. */ + public String product; // NOCHK + /** The version of the database. */ + public String version; // NOCHK + } + + /** + * Extractor to retrieve connection meta data information. This class uses reflection to get the + * information from the connection. To ensure high performance it caches the reflection + * Method objects using the {@link ReflectionCache}. + * + * @author Stefan Siegl + */ + static class ConnectionMetaDataExtractor { + + /** Method names. */ + private static final String GET_META_DATA = "getMetaData"; + /** Method names. */ + private static final String GET_URL = "getURL"; + /** Method names. */ + private static final String GET_DATABASE_PRODUCT_VERSION = "getDatabaseProductVersion"; + /** Method names. */ + private static final String GET_DATABASE_PRODUCT_NAME = "getDatabaseProductName"; + + /** Extractor for the JDBC URL. */ + static JDBCUrlExtractor urlExtractor = new JDBCUrlExtractor(); + /** Cache for the Method elements. */ + static ReflectionCache cache = new ReflectionCache(); + + /** + * The logger of this class. Initialized manually. + */ + static Logger logger = LoggerFactory.getLogger(ConnectionMetaDataExtractor.class); + + /** + * Parses a given Connection and retrieves the monitoring-related meta + * information. + * + * @param connection + * the Connection object. + * @return meta information about the connection for monitoring. + */ + public ConnectionMetaData parse(Object connection) { + ConnectionMetaData data = new ConnectionMetaData(); + if (null == connection) { + logger.warn("Meta Information on database cannot be read. No database details like URL or Vendor will be displayed."); + return data; + } + + Object metaData = getMetaData(connection.getClass(), connection); + if (null == metaData) { + logger.warn("Meta Information on database cannot be read. No database details like URL or Vendor will be displayed."); + return data; + } + + Class metaDataClass = metaData.getClass(); + + data.version = parseVersion(metaDataClass, metaData); + data.url = parseTarget(metaDataClass, metaData); + data.product = parseProduct(metaDataClass, metaData); + + return data; + } + + /** + * Retrieves the meta information object from the connection. + * + * @param connectionClass + * the connection class. + * @param connection + * the connection instance. + * @return the meta information object from the connection or null in case of + * problems. + */ + private Object getMetaData(Class connectionClass, Object connection) { + return cache.invokeMethod(connectionClass, GET_META_DATA, null, connection, null, null); + } + + /** + * Retrieves the target/url from the jdbc connection string. + * + * @param databaseMetaDataClass + * the meta information class. + * @param databaseMetaData + * the meta information object of the connection. + * @return the target/url from the jdbc connection string. + */ + private String parseTarget(Class databaseMetaDataClass, Object databaseMetaData) { + String url = (String) cache.invokeMethod(databaseMetaDataClass, GET_URL, null, databaseMetaData, null, null); + return urlExtractor.extractURLfromJDBCURL(url); + } + + /** + * Retrieves the version of the database. + * + * @param databaseMetaDataClass + * the meta information class. + * @param databaseMetaData + * the meta information instance of the connection. + * @return the version of the database. + */ + private String parseVersion(Class databaseMetaDataClass, Object databaseMetaData) { + return (String) cache.invokeMethod(databaseMetaDataClass, GET_DATABASE_PRODUCT_VERSION, null, databaseMetaData, null, null); + } + + /** + * Retrieves the product name of the database. + * + * @param databaseMetaDataClass + * the meta information class. + * @param databaseMetaData + * the meta information instance of the connection. + * @return the product name of the database. + */ + private String parseProduct(Class databaseMetaDataClass, Object databaseMetaData) { + return (String) cache.invokeMethod(databaseMetaDataClass, GET_DATABASE_PRODUCT_NAME, null, databaseMetaData, null, null); + } + } + + /** + * Extractor to retrieve the concrete URL from the JDBC connection string. + * + * @author Stefan Siegl + */ + static class JDBCUrlExtractor { + /** + * URL pattern to read jdbc URL from jdbc connection string. + * jdbc:sqlserver://[serverName[\instanceName + * ][:portNumber]][;property=value[;property=value]] + * jdbc:db2://:/ --> remove the // + * jdbc:h2:../../database/database/dvdstore22 + * + * Oracle is once again different: http://www.orafaq.com/wiki/JDBC + * "jdbc:oracle:thin:@//myhost:1521/orcl"; "jdbc:oracle:thin:@myhost:1521:orcl"; + * "jdbc:oracle:oci:@myhost:1521:orcl"; + * + * use: http://www.regexr.com/ to play around with regex. See + * http://www.regular-expressions.info/named.html as great reference. + */ + private final Pattern urlPattern = Pattern.compile("^jdbc:(?:oracle:.*?|.*?):(?:[@/]*)?(.*?)([;?].*)?$"); + + /** + * Extracts the url from the connection string. + * + * @param url + * the connection string + * @return the url. + */ + public String extractURLfromJDBCURL(String url) { + try { + final Matcher matcher = urlPattern.matcher(url); + matcher.find(); + return matcher.group(1); + } catch (IllegalStateException i) { + return url; + } + } + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionSensor.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionSensor.java new file mode 100644 index 000000000..a3cdd2708 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionSensor.java @@ -0,0 +1,62 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import info.novatec.inspectit.agent.hooking.IHook; +import info.novatec.inspectit.agent.sensor.method.AbstractMethodSensor; +import info.novatec.inspectit.agent.sensor.method.IMethodSensor; + +import java.sql.PreparedStatement; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; + +/** + * This sensor initializes the {@link ConnectionHook} to intercept the creation of + * {@link PreparedStatement} classes. + * + * @author Patrice Bouillet + * + */ +public class ConnectionSensor extends AbstractMethodSensor implements IMethodSensor { + + /** + * The statement storage. + */ + @Autowired + private StatementStorage statementStorage; + + /** + * The used prepared statement hook. + */ + private ConnectionHook connectionHook = null; + + /** + * No-arg constructor needed for Spring. + */ + public ConnectionSensor() { + } + + /** + * The default constructor which needs one parameter for initialization. + * + * @param statementStorage + * The statement storage. + */ + public ConnectionSensor(StatementStorage statementStorage) { + this.statementStorage = statementStorage; + } + + /** + * {@inheritDoc} + */ + public IHook getHook() { + return connectionHook; + } + + /** + * {@inheritDoc} + */ + public void init(Map parameter) { + connectionHook = new ConnectionHook(statementStorage); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementHook.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementHook.java new file mode 100644 index 000000000..8e62a193a --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementHook.java @@ -0,0 +1,222 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.hooking.IConstructorHook; +import info.novatec.inspectit.agent.hooking.IMethodHook; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.util.StringConstraint; +import info.novatec.inspectit.util.ThreadLocalStack; +import info.novatec.inspectit.util.Timer; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This hook is intended to intercept the created prepared statement calls to the database. To not + * create duplicate calls, a {@link ThreadLocal} attribute is used to check if this specific + * statement is already 'sent'. + *

+ * Furthermore, a {@link StatementStorage} is used which saves all the created prepared statements + * and if the parameter hook for the sqls is installed and activated, the parameters are replaced. + * + * @author Patrice Bouillet + * + */ +public class PreparedStatementHook implements IMethodHook, IConstructorHook { + + /** + * The logger of this class. Initialized manually. + */ + Logger log = LoggerFactory.getLogger(PreparedStatementHook.class); + + /** + * The stack containing the start time values. + */ + private final ThreadLocalStack timeStack = new ThreadLocalStack(); + + /** + * The timer used for accurate measuring. + */ + private final Timer timer; + + /** + * The ID manager. + */ + private final IIdManager idManager; + + /** + * The statement storage. + */ + private final StatementStorage statementStorage; + + /** + * Storage for connection meta data. + */ + private final ConnectionMetaDataStorage connectionMetaDataStorage; + + /** + * The ThreadLocal for a boolean value so only the last before and first after hook of an + * invocation is measured. + */ + private ThreadLocal threadLast = new ThreadLocal(); + + /** + * The StringConstraint to ensure a maximum length of strings. + */ + private StringConstraint strConstraint; + + /** + * Caches the calls to getConnection(). + */ + private StatementReflectionCache statementReflectionCache; + + /** + * Contains all method idents of all prepared statements that had a problem finding the stored + * SQL statement. Using this structure we can ensure that we do not throw the exception always + * again. + */ + private static List preparedStatementsWithExceptions = new ArrayList(0); + + /** + * The only constructor which needs the {@link Timer}. + * + * @param timer + * The timer. + * @param idManager + * The ID manager. + * @param statementStorage + * The statement storage. + * @param parameter + * Additional parameters. + * @param connectionMetaDataStorage + * the meta information storage for connections. + * @param statementReflectionCache + * Caches the calls to getConnection(). + */ + public PreparedStatementHook(Timer timer, IIdManager idManager, StatementStorage statementStorage, ConnectionMetaDataStorage connectionMetaDataStorage, + StatementReflectionCache statementReflectionCache, Map parameter) { + this.timer = timer; + this.idManager = idManager; + this.statementStorage = statementStorage; + this.connectionMetaDataStorage = connectionMetaDataStorage; + this.strConstraint = new StringConstraint(parameter); + this.statementReflectionCache = statementReflectionCache; + } + + /** + * {@inheritDoc} + */ + public void beforeBody(long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc) { + timeStack.push(new Double(timer.getCurrentTime())); + threadLast.set(Boolean.TRUE); + } + + /** + * {@inheritDoc} + */ + public void firstAfterBody(long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) { + timeStack.push(new Double(timer.getCurrentTime())); + } + + /** + * {@inheritDoc} + */ + public void secondAfterBody(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) { + double endTime = timeStack.pop().doubleValue(); + double startTime = timeStack.pop().doubleValue(); + + if (threadLast.get().booleanValue()) { + threadLast.set(Boolean.FALSE); + + String sql = statementStorage.getPreparedStatement(object); + if (null != sql) { + double duration = endTime - startTime; + SqlStatementData sqlData = (SqlStatementData) coreService.getMethodSensorData(sensorTypeId, methodId, sql); + if (null == sqlData) { + try { + Timestamp timestamp = new Timestamp(System.currentTimeMillis() - Math.round(duration)); + List params = statementStorage.getParameters(object); + long platformId = idManager.getPlatformId(); + long registeredSensorTypeId = idManager.getRegisteredSensorTypeId(sensorTypeId); + long registeredMethodId = idManager.getRegisteredMethodId(methodId); + + sqlData = new SqlStatementData(timestamp, platformId, registeredSensorTypeId, registeredMethodId); + sqlData.setPreparedStatement(true); + sqlData.setSql(strConstraint.crop(sql)); + sqlData.setDuration(duration); + sqlData.calculateMin(duration); + sqlData.calculateMax(duration); + sqlData.setCount(1L); + sqlData.setParameterValues(params); + + // populate the connection meta data. + connectionMetaDataStorage.populate(sqlData, statementReflectionCache.getConnection(object.getClass(), object)); + + coreService.addMethodSensorData(sensorTypeId, methodId, sql, sqlData); + } catch (IdNotAvailableException e) { + if (log.isDebugEnabled()) { + log.debug("Could not save the sql data because of an unavailable id. " + e.getMessage()); + } + } + } else { + sqlData.increaseCount(); + sqlData.addDuration(duration); + + sqlData.calculateMin(duration); + sqlData.calculateMax(duration); + } + } else { + // the sql was not found, we'll try again + threadLast.set(Boolean.TRUE); + } + } + + } + + /** + * {@inheritDoc} + */ + public void beforeConstructor(long methodId, long sensorTypeId, Object[] parameters, RegisteredSensorConfig rsc) { + } + + /** + * {@inheritDoc} + */ + public void afterConstructor(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc) { + try { + statementStorage.addPreparedStatement(object); + } catch (NoSuchElementException e) { + // Ensure that a problem with this statement is only thrown once to not spam the log + // file. It is possible that we hide exceptions. + Long methodIdLong = Long.valueOf(methodId); + if (preparedStatementsWithExceptions.contains(methodIdLong)) { + // we already logged this exception... + return; + } + + // it is possible that this exception is thrown in a 'normal' way, + // as everyone could instantiate a prepared statement object without + // calling first a method on the connection (prepareStatement...) + log.info("Could not add prepared statement, no sql available! Method ID(local): " + methodId); + log.info( + "This is not an inspectIT issue, but you forgot to integrate the Connection creating the SQL statement in the configuration, please consult the management of inspectIT and send the following stacktrace!", + e); + + // we need to ensure thread safety for the list and do not care for lost updates, so + // we simply create a new list based on the old list and change references after we + // finished building it. + List clonedList = new ArrayList(preparedStatementsWithExceptions); + clonedList.add(methodIdLong); + preparedStatementsWithExceptions = clonedList; + } + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementParameterHook.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementParameterHook.java new file mode 100644 index 000000000..7cb0bfdd5 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementParameterHook.java @@ -0,0 +1,101 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.hooking.IMethodHook; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This hook is intended to intercept the methods which are used to set some specific parameter + * values on the prepared statements so that the final prepared statement with all inserted values + * can be generated in the user interface. + * + * @author Patrice Bouillet + * + */ +public class PreparedStatementParameterHook implements IMethodHook { + + /** + * The statement storage to add the statements to. + */ + private StatementStorage statementStorage; + + /** + * The ThreadLocal for a boolean value so only the last before and first after hook of an + * invocation is measured. + */ + private static ThreadLocal threadLast = new ThreadLocal(); + + /** + * Map that holds the set methods for which we will not load the values via toString() method, + * but will have an assigned value given as the key in this map. + */ + private static final Map METHOD_VALUE_MAP; + + static { + METHOD_VALUE_MAP = new HashMap(); + METHOD_VALUE_MAP.put("setNull", null); + METHOD_VALUE_MAP.put("setAsciiStream", "[AsciiStream]"); + METHOD_VALUE_MAP.put("setBinaryStream", "[BinaryStream]"); + METHOD_VALUE_MAP.put("setBlob", "[Blob]"); + METHOD_VALUE_MAP.put("setCharacterStream", "[CharacterStream]"); + METHOD_VALUE_MAP.put("setClob", "[Clob]"); + METHOD_VALUE_MAP.put("setNCharacterStream", "[NCharacterStream]"); + METHOD_VALUE_MAP.put("setNClob", "[NClob]"); + METHOD_VALUE_MAP.put("setUnicodeStream", "[UnicodeStream]"); + } + + /** + * Default constructor which needs a reference to the statement storage. + * + * @param statementStorage + * The statement storage. + */ + public PreparedStatementParameterHook(StatementStorage statementStorage) { + this.statementStorage = statementStorage; + } + + /** + * {@inheritDoc} + */ + public void beforeBody(long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc) { + threadLast.set(Boolean.TRUE); + } + + /** + * {@inheritDoc} + */ + public void firstAfterBody(long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) { + // nothing to do + } + + /** + * {@inheritDoc} + */ + public void secondAfterBody(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) { + if (threadLast.get().booleanValue()) { + threadLast.set(Boolean.FALSE); + + List parameterTypes = rsc.getParameterTypes(); + if (METHOD_VALUE_MAP.containsKey(rsc.getTargetMethodName()) && parameterTypes.size() >= 1 && "int".equals(parameterTypes.get(0))) { + // subtract one as the index starts at 1, and not at 0 + int index = ((Integer) parameters[0]).intValue() - 1; + Object value = METHOD_VALUE_MAP.get(rsc.getTargetMethodName()); + + statementStorage.addParameter(object, index, value); + } else if (parameterTypes.size() >= 2 && "int".equals(parameterTypes.get(0))) { + // subtract one as the index starts at 1, and not at 0 + int index = ((Integer) parameters[0]).intValue() - 1; + Object value = parameters[1]; + + statementStorage.addParameter(object, index, value); + } else if ("clearParameters".equals(rsc.getTargetMethodName())) { + statementStorage.clearParameters(object); + } + } + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementParameterSensor.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementParameterSensor.java new file mode 100644 index 000000000..991187154 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementParameterSensor.java @@ -0,0 +1,58 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import info.novatec.inspectit.agent.hooking.IHook; +import info.novatec.inspectit.agent.sensor.method.AbstractMethodSensor; +import info.novatec.inspectit.agent.sensor.method.IMethodSensor; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author Patrice Bouillet + * + */ +public class PreparedStatementParameterSensor extends AbstractMethodSensor implements IMethodSensor { + + /** + * The statement storage. + */ + @Autowired + private StatementStorage statementStorage; + + /** + * The used prepared statement hook. + */ + private PreparedStatementParameterHook preparedStatementParameterHook = null; + + /** + * No-arg constructor needed for Spring. + */ + public PreparedStatementParameterSensor() { + } + + /** + * The default constructor which needs one parameter for initialization. + * + * @param statementStorage + * The statement storage. + */ + public PreparedStatementParameterSensor(StatementStorage statementStorage) { + this.statementStorage = statementStorage; + } + + /** + * {@inheritDoc} + */ + public IHook getHook() { + return preparedStatementParameterHook; + } + + /** + * {@inheritDoc} + */ + public void init(Map parameter) { + preparedStatementParameterHook = new PreparedStatementParameterHook(statementStorage); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementSensor.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementSensor.java new file mode 100644 index 000000000..97a981539 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementSensor.java @@ -0,0 +1,68 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.hooking.IHook; +import info.novatec.inspectit.agent.sensor.method.AbstractMethodSensor; +import info.novatec.inspectit.agent.sensor.method.IMethodSensor; +import info.novatec.inspectit.util.Timer; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; + +/** + * @author Patrice Bouillet + * + */ +public class PreparedStatementSensor extends AbstractMethodSensor implements IMethodSensor { + + /** + * The timer used for accurate measuring. + */ + @Autowired + private Timer timer; + + /** + * The ID manager. + */ + @Autowired + private IIdManager idManager; + + /** + * The statement storage. + */ + @Autowired + private StatementStorage statementStorage; + + /** + * Caches the calls to getConnection(). + */ + @Autowired + private StatementReflectionCache statementReflectionCache; + + /** + * Storage for connection meta data. + */ + @Autowired + private ConnectionMetaDataStorage connectionMetaDataStorage; + + /** + * The used prepared statement hook. + */ + private PreparedStatementHook preparedStatementHook = null; + + /** + * {@inheritDoc} + */ + public IHook getHook() { + return preparedStatementHook; + } + + /** + * {@inheritDoc} + */ + public void init(Map parameter) { + preparedStatementHook = new PreparedStatementHook(timer, idManager, statementStorage, connectionMetaDataStorage, statementReflectionCache, parameter); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/StatementHook.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/StatementHook.java new file mode 100644 index 000000000..5784d15a2 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/StatementHook.java @@ -0,0 +1,158 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.core.impl.CoreService; +import info.novatec.inspectit.agent.hooking.IMethodHook; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.util.StringConstraint; +import info.novatec.inspectit.util.ThreadLocalStack; +import info.novatec.inspectit.util.Timer; + +import java.sql.Timestamp; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The hook implementation for the statement sensor. It uses the {@link ThreadLocalStack} class to + * know if some execute methods call each other which would result in multiple data objects for only + * one query. After the complete SQL method was executed, it computes how long the method took to + * finish and saves the executed SQL Statement String. Afterwards, the measurement is added to the + * {@link CoreService}. + * + * @author Christian Herzog + * @author Patrice Bouillet + * + */ +public class StatementHook implements IMethodHook { + + /** + * The logger of this class. Initialized manually. + */ + private static final Logger LOG = LoggerFactory.getLogger(StatementHook.class); + + /** + * The stack containing the start time values. + */ + private final ThreadLocalStack timeStack = new ThreadLocalStack(); + + /** + * The timer used for accurate measuring. + */ + private final Timer timer; + + /** + * The ID manager. + */ + private final IIdManager idManager; + + /** + * Storage for connection meta data. + */ + private final ConnectionMetaDataStorage connectionMetaDataStorage; + + /** + * The ThreadLocal for a boolean value so only the last before and first after hook of an + * invocation is measured. + */ + private ThreadLocal threadLast = new ThreadLocal(); + + /** + * The StringConstraint to ensure a maximum length of strings. + */ + private StringConstraint strConstraint; + + /** + * Caches the calls to getConnection(). + */ + private StatementReflectionCache statementReflectionCache; + + /** + * The only constructor which needs the {@link Timer}. + * + * @param timer + * The timer. + * @param idManager + * The ID manager. + * @param parameter + * Additional parameters. + * @param connectionMetaDataStorage + * the storage containing meta information on the connection. + * @param statementReflectionCache + * Caches the calls to getConnection() + */ + public StatementHook(Timer timer, IIdManager idManager, ConnectionMetaDataStorage connectionMetaDataStorage, StatementReflectionCache statementReflectionCache, Map parameter) { + this.timer = timer; + this.idManager = idManager; + this.connectionMetaDataStorage = connectionMetaDataStorage; + this.strConstraint = new StringConstraint(parameter); + this.statementReflectionCache = statementReflectionCache; + } + + /** + * {@inheritDoc} + */ + public void beforeBody(long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc) { + timeStack.push(new Double(timer.getCurrentTime())); + threadLast.set(Boolean.TRUE); + } + + /** + * {@inheritDoc} + */ + public void firstAfterBody(long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) { + timeStack.push(new Double(timer.getCurrentTime())); + } + + /** + * {@inheritDoc} + */ + public void secondAfterBody(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) { + double endTime = timeStack.pop().doubleValue(); + double startTime = timeStack.pop().doubleValue(); + + if (threadLast.get().booleanValue()) { + threadLast.set(Boolean.FALSE); + + double duration = endTime - startTime; + String sql = parameters[0].toString(); + SqlStatementData sqlData = (SqlStatementData) coreService.getMethodSensorData(sensorTypeId, methodId, sql); + + if (null == sqlData) { + try { + Timestamp timestamp = new Timestamp(System.currentTimeMillis() - Math.round(duration)); + long platformId = idManager.getPlatformId(); + long registeredSensorTypeId = idManager.getRegisteredSensorTypeId(sensorTypeId); + long registeredMethodId = idManager.getRegisteredMethodId(methodId); + + sqlData = new SqlStatementData(timestamp, platformId, registeredSensorTypeId, registeredMethodId); + sqlData.setPreparedStatement(false); + sqlData.setSql(strConstraint.crop(sql)); + sqlData.setDuration(duration); + sqlData.calculateMin(duration); + sqlData.calculateMax(duration); + sqlData.setCount(1L); + + // populate the connection meta data. + connectionMetaDataStorage.populate(sqlData, statementReflectionCache.getConnection(object.getClass(), object)); + coreService.addMethodSensorData(sensorTypeId, methodId, sql, sqlData); + } catch (IdNotAvailableException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Could not save the sql data because of an unavailable id. " + e.getMessage()); + } + } + } else { + sqlData.increaseCount(); + sqlData.addDuration(duration); + + sqlData.calculateMin(duration); + sqlData.calculateMax(duration); + } + } + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/StatementReflectionCache.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/StatementReflectionCache.java new file mode 100644 index 000000000..ded2be9ed --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/StatementReflectionCache.java @@ -0,0 +1,32 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import info.novatec.inspectit.util.ReflectionCache; + +import java.sql.Connection; + +import org.springframework.stereotype.Component; + +/** + * Provides the connection for a given statement. + * + * @author Stefan Siegl + */ +@Component +public class StatementReflectionCache extends ReflectionCache { + + /** Caches the method name. */ + private static final String GET_CONNECTION_METHOD_NAME = "getConnection"; + + /** + * Retrieves the connection. + * + * @param statementClass + * the class of the statement instance. + * @param statementInstance + * the instance of the statement. + * @return the associated connection. + */ + public Connection getConnection(Class statementClass, Object statementInstance) { + return (Connection) invokeMethod(statementClass, GET_CONNECTION_METHOD_NAME, null, statementInstance, null, null); + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/StatementSensor.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/StatementSensor.java new file mode 100644 index 000000000..3eaa0467e --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/StatementSensor.java @@ -0,0 +1,67 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.hooking.IHook; +import info.novatec.inspectit.agent.sensor.method.AbstractMethodSensor; +import info.novatec.inspectit.agent.sensor.method.IMethodSensor; +import info.novatec.inspectit.util.Timer; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; + +/** + * The SQL timer sensor which initializes and returns the {@link StatementHook} class. + * + * @author Christian Herzog + * + */ +public class StatementSensor extends AbstractMethodSensor implements IMethodSensor { + + /** + * The timer used for accurate measuring. + */ + @Autowired + private Timer timer; + + /** + * The ID manager. + */ + @Autowired + private IIdManager idManager; + + /** + * The used statement hook. + */ + private StatementHook statementHook = null; + + /** + * The default constructor which needs 2 parameter for initialization. Caches the calls to + * getConnection(). + */ + @Autowired + private StatementReflectionCache statementReflectionCache; + + /** + * Storage for connection meta data. + */ + @Autowired + private ConnectionMetaDataStorage connectionMetaDataStorage; + + /** + * Returns the method hook. + * + * @return The method hook. + */ + public IHook getHook() { + return statementHook; + } + + /** + * {@inheritDoc} + */ + public void init(Map parameter) { + statementHook = new StatementHook(timer, idManager, connectionMetaDataStorage, statementReflectionCache, parameter); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/StatementStorage.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/StatementStorage.java new file mode 100644 index 000000000..543ee5afc --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/jdbc/StatementStorage.java @@ -0,0 +1,298 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import info.novatec.inspectit.util.ThreadLocalStack; + +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +/** + * Stores the mapping between statements and objects so that these statements are later accessible. + * + * @author Patrice Bouillet + * @author Stefan Siegl + */ +@Component +public class StatementStorage { + + /** + * The logger of this class. Initialized manually. + */ + private static final Logger LOG = LoggerFactory.getLogger(StatementStorage.class); + + /** representation of a null value. */ + private static final String NULL_VALUE = "null"; + + /** + * This cache keeps track of the prepared statement objects and associates these with the + * concrete query string and its the bound parameters. Weak keys ensure that elements will be + * cleared regularly. As the key is the PreparedStatement object, as soon as the application + * looses the reference to this object (which is usually very quickly after the invocation), the + * garbage collector can remove this object and thus the entry in the cache. In order to + * safe-guard our cache, elements will also be removed if they are not used for 20 minutes. + * + * Note that this data structure provides atomic access like a ConcurrentMap. + * . + */ + private Cache preparedStatements = CacheBuilder.newBuilder().expireAfterAccess(20 * 60, TimeUnit.SECONDS).weakKeys().build(); + + /** + * Returns the sql thread local stack. + */ + private ThreadLocalStack sqlThreadLocalStack = new ThreadLocalStack(); + + /** + * Adds a prepared statement to this storage for later retrieval. + * + * @param object + * The object which will be the key of the mapping. + */ + public void addPreparedStatement(Object object) { + String sql = sqlThreadLocalStack.getLast(); + preparedStatements.put(object, new QueryInformation(sql)); + + if (LOG.isDebugEnabled()) { + LOG.debug("Recorded prepared sql statement: " + sql); + } + } + + /** + * Returns a stored sql string for this object. + * + * @param object + * The object which will be used to look up in the map. + * @return The sql string or null if this statement is not available within the + * storage. + */ + protected String getPreparedStatement(Object object) { + QueryInformation queryAndParameters = preparedStatements.getIfPresent(object); + + String query = null; + if (null != queryAndParameters) { + query = queryAndParameters.query; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Return prepared sql statement: " + query); + } + + return query; + } + + /** + * Returns a stored parameters for the object. + * + * @param object + * The object which will be used to look up in the map. + * @return The list of parameters or null if there is no container for the given + * SQL statement or there are no parameters captured within this SQL statement. + */ + protected List getParameters(Object object) { + QueryInformation queryAndParameters = preparedStatements.getIfPresent(object); + if (null == queryAndParameters) { + return null; + } else { + return queryAndParameters.getParametersAsList(); + } + } + + /** + * Adds a parameter to a specific prepared statement. + * + * @param preparedStatement + * The prepared statement object. + * @param index + * The index of the value. + * @param value + * The value to be inserted. + */ + protected void addParameter(Object preparedStatement, int index, Object value) { + QueryInformation queryAndParameters = preparedStatements.getIfPresent(preparedStatement); + + if (null == queryAndParameters) { + if (LOG.isDebugEnabled()) { + LOG.debug("Could not get the prepared statement from the cache to add a parameter! Prepared Statement:" + preparedStatement + " index:" + index + " value:" + value); + } + return; + } + + String[] parameters = queryAndParameters.getParameters(); + + if (parameters.length <= index) { + if (LOG.isWarnEnabled()) { + LOG.warn("Trying to set the parameter with value " + value + " at index " + index + ", but the prepared statement did not have this parameter."); + } + return; + } + + if (null != value) { + if (value instanceof String || value instanceof Date || value instanceof Time || value instanceof Timestamp) { + parameters[index] = "'" + value.toString() + "'"; + } else { + parameters[index] = value.toString(); + } + } else { + value = NULL_VALUE; + parameters[index] = (String) value; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Prepared Statement :: Added value:" + value.toString() + " with index:" + index + " to prepared statement:" + preparedStatement); + } + } + + /** + * Clears all the parameters in the array. + * + * @param preparedStatement + * The prepared statement for which all parameters are going to be cleared. + */ + protected void clearParameters(Object preparedStatement) { + QueryInformation queryAndParameters = preparedStatements.getIfPresent(preparedStatement); + + if (null == queryAndParameters) { + if (LOG.isDebugEnabled()) { + LOG.debug("Could not get the prepared statement from the cache to clear the parameters! Prepared Statement:" + preparedStatement); + } + return; + } + + queryAndParameters.clearParameters(); + } + + /** + * This method adds an SQL String to the current thread local stack. This is needed so that + * created prepared statements can be associated to the SQL Strings. + *

+ * So if three times the prepared statement method is called with the same string, the stack + * contains the string three times. Now the Prepared Statement is created which results in + * calling the {@link #addPreparedStatement(Object, String)} method. The last added String is + * taken and associated with the object. + * + * @param sql + * The SQL String. + */ + protected void addSql(String sql) { + sqlThreadLocalStack.push(sql); + } + + /** + * Removes the last added sql from the thread local stack. We don't need the String object here. + */ + protected void removeSql() { + sqlThreadLocalStack.pop(); + } + + /** + * Value container to store the SQL query and its parameters within the cache of prepared + * statements. The JDBC sensor in inspectIT allows for two modes. The SQL query can be enhanced + * with the values of the bind parameters. This happens if and only if + * "SQL Prepared Statement Parameter Replacement" is set. Thus this container ensures that this + * calculation is only done if this is needed, that is when the parameters are first accessed. + * Thus ensure that you only access the parameters if you really want to fill them! + * + *

+ * To access the parameters during the "filling stage", prefer the + * public String[] getParameters() method as this allows to access the internal + * String[]. + * + * @author Stefan Siegl + */ + private static class QueryInformation { + /** the SQL query. */ + private String query; + + /** + * internal container of the SQL bind values. The size of this array defines the number of + * bind values. This field is filled on first access. + */ + private String[] parameters = null; + + /** + * Creates a new instance of this value container. Please note that creating an instance of + * this class does not calculate the number of available bind values (a.k.a. "parameters"). + * They are creating on first use. + * + * @param query + * The SQL query. + */ + public QueryInformation(String query) { + this.query = query; + } + + /** + * Returns the String[] representation of the bind values of this SQL query. This method + * should be used over the List getParametersAsList method to fill the + * parameters as this method provides access to the backing String[] and is thus more + * efficient. + * + * please note that the calculation of the number of parameters within the SQL query is + * done with the first access to this method. Thus only call this method if you know that + * you do have parameters to set, else there will be unnecessary calculations. + * + * @return String[] containing the current bind values of this SQL query. The + * size of the array can be used to deduce the number of available bind parameters + * based on the SQL query. + */ + public String[] getParameters() { + if (null == parameters) { + // Calculate the amount of parameters based on the SQL query. We calculate this + // value on first request as this is only needed if we have parameter capturing + // active. + int count = 0; + for (int i = query.length() - 1; i > 0; i--) { + if (query.charAt(i) == '?') { + count++; + } + } + parameters = new String[count]; + } + + return parameters; // NOPMD: no copy to improve performance + } + + /** + * Resets the bind parameters of this SQL query. + * + * please note that the calculation of the number of parameters within the SQL query + * will be done as a side-effect of this method (but only if it is not already done before). + * Thus only call this method if you know that you do have parameters to set, else there + * will be unnecessary calculations. + */ + public void clearParameters() { + if (null == parameters) { + return; + } + parameters = new String[parameters.length]; + } + + /** + * Returns the parameter values as List. The list will provide a + * representation of the parameters. Adding to this list will not change the + * parameters. This method is meant to be used from the second after hook to report the + * current parameters. + *

+ * Please note that to fill the parameters the String[] should be used. + * + * @return the parameter values as List or null if no + * parameters are captured. + */ + public List getParametersAsList() { + if (null == parameters) { + return null; + } else { + return Arrays.asList(parameters); + } + } + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/AggregateTimerStorage.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/AggregateTimerStorage.java new file mode 100644 index 000000000..476416950 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/AggregateTimerStorage.java @@ -0,0 +1,63 @@ +package info.novatec.inspectit.agent.sensor.method.timer; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.ParameterContentData; +import info.novatec.inspectit.communication.valueobject.TimerRawVO; + +import java.sql.Timestamp; +import java.util.List; + +/** + * This timer storage just stores the passed value in the a {@link PlainTimerValueObject}, without + * computing anything. When {@link #finalizeValueObject()} is called, it computes all the values + * (min, max, variance etc.) and stores it in a {@link VarianceTimerValueObject}. + * + * @author Patrice Bouillet + * + */ +public class AggregateTimerStorage implements ITimerStorage { + + /** + * The raw value object. + */ + private TimerRawVO timerRawVO; + + /** + * Default constructor which initializes a {@link TimerRawVO} object. + * + * @param timeStamp + * The time stamp. + * @param platformIdent + * The platform ID. + * @param sensorTypeIdent + * The sensor type ID. + * @param methodIdent + * The method ID. + * @param parameterContentData + * The content of the parameter/fields. + * @param charting + * If TimerData's charting should be set or not. + */ + public AggregateTimerStorage(Timestamp timeStamp, long platformIdent, long sensorTypeIdent, long methodIdent, List parameterContentData, boolean charting) { + timerRawVO = new TimerRawVO(timeStamp, platformIdent, sensorTypeIdent, methodIdent, parameterContentData, charting); + } + + /** + * {@inheritDoc} + */ + public void addData(double time, double cpuTime) { + if (cpuTime < 0) { + timerRawVO.add(time); + } else { + timerRawVO.add(time, cpuTime); + } + } + + /** + * {@inheritDoc} + */ + public DefaultData finalizeDataObject() { + return timerRawVO.finalizeData(); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/ITimerStorage.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/ITimerStorage.java new file mode 100644 index 000000000..04f1fe861 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/ITimerStorage.java @@ -0,0 +1,23 @@ +package info.novatec.inspectit.agent.sensor.method.timer; + +import info.novatec.inspectit.agent.core.IObjectStorage; + +/** + * A {@link ITimerStorage} just accepts time data through {@link #addData(double, double)}. + * + * @author Patrice Bouillet + * + */ +public interface ITimerStorage extends IObjectStorage { + + /** + * The only method, which is used to process the new time and cpu time value. + * + * @param time + * The time value. + * @param cpuTime + * The cpu time value. + */ + void addData(double time, double cpuTime); + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/OptimizedTimerStorage.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/OptimizedTimerStorage.java new file mode 100644 index 000000000..82cbda160 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/OptimizedTimerStorage.java @@ -0,0 +1,72 @@ +package info.novatec.inspectit.agent.sensor.method.timer; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.ParameterContentData; +import info.novatec.inspectit.communication.data.TimerData; + +import java.sql.Timestamp; +import java.util.List; + +/** + * The optimized timer storage instantly computes the new values and saves them in the + * {@link TimerData}. + * + * @author Patrice Bouillet + * + */ +public class OptimizedTimerStorage implements ITimerStorage { + + /** + * The used {@link TimerData}. + */ + private TimerData timerData; + + /** + * Default constructor which initializes a {@link TimerData} object. + * + * @param timeStamp + * The time stamp. + * @param platformIdent + * The platform ID. + * @param sensorTypeIdent + * The sensor type ID. + * @param methodIdent + * The method ID. + * @param parameterContentData + * The content of the parameter/fields. + * @param charting + * If TimerData's charting should be set or not. + */ + public OptimizedTimerStorage(Timestamp timeStamp, long platformIdent, long sensorTypeIdent, long methodIdent, List parameterContentData, boolean charting) { + timerData = new TimerData(timeStamp, platformIdent, sensorTypeIdent, methodIdent, parameterContentData); + timerData.setCharting(charting); + } + + /** + * {@inheritDoc} + */ + public void addData(double time, double cpuTime) { + timerData.increaseCount(); + timerData.addDuration(time); + + timerData.calculateMax(time); + timerData.calculateMin(time); + + // only add the cpu time if it greater than zero + if (cpuTime >= 0) { + timerData.addCpuDuration(cpuTime); + + timerData.calculateCpuMax(cpuTime); + timerData.calculateCpuMin(cpuTime); + } + } + + /** + * {@inheritDoc} + */ + public DefaultData finalizeDataObject() { + // processing is done during data adding, so nothing to do here. + return timerData; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/PlainTimerStorage.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/PlainTimerStorage.java new file mode 100644 index 000000000..985670f69 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/PlainTimerStorage.java @@ -0,0 +1,62 @@ +package info.novatec.inspectit.agent.sensor.method.timer; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.ParameterContentData; +import info.novatec.inspectit.communication.valueobject.TimerRawVO; + +import java.sql.Timestamp; +import java.util.List; + +/** + * Class which stores the data as they arrive without further processing. This will increase memory + * usage by a high amount but should reduces CPU usage. + * + * @author Patrice Bouillet + * + */ +public class PlainTimerStorage implements ITimerStorage { + + /** + * The raw value object. + */ + private TimerRawVO timerRawVO; + + /** + * Default constructor which initializes a {@link TimerRawVO} object. + * + * @param timeStamp + * The time stamp. + * @param platformIdent + * The platform ID. + * @param sensorTypeIdent + * The sensor type ID. + * @param methodIdent + * The method ID. + * @param parameterContentData + * The content of the parameter/fields. + * @param charting + * If TimerData's charting should be set or not. + */ + public PlainTimerStorage(Timestamp timeStamp, long platformIdent, long sensorTypeIdent, long methodIdent, List parameterContentData, boolean charting) { + timerRawVO = new TimerRawVO(timeStamp, platformIdent, sensorTypeIdent, methodIdent, parameterContentData, charting); + } + + /** + * {@inheritDoc} + */ + public void addData(double time, double cpuTime) { + if (cpuTime < 0) { + timerRawVO.add(time); + } else { + timerRawVO.add(time, cpuTime); + } + } + + /** + * {@inheritDoc} + */ + public DefaultData finalizeDataObject() { + return timerRawVO; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/TimerHook.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/TimerHook.java new file mode 100644 index 000000000..1e5f60124 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/TimerHook.java @@ -0,0 +1,235 @@ +package info.novatec.inspectit.agent.sensor.method.timer; + +import info.novatec.inspectit.agent.config.IPropertyAccessor; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.hooking.IConstructorHook; +import info.novatec.inspectit.agent.hooking.IMethodHook; +import info.novatec.inspectit.agent.sensor.method.averagetimer.AverageTimerHook; +import info.novatec.inspectit.communication.data.ParameterContentData; +import info.novatec.inspectit.util.StringConstraint; +import info.novatec.inspectit.util.ThreadLocalStack; +import info.novatec.inspectit.util.Timer; + +import java.lang.management.ThreadMXBean; +import java.sql.Timestamp; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The hook implementation for the timer sensor. It uses the {@link ThreadLocalStack} class to save + * the time when the method was called. + *

+ * The difference to the {@link AverageTimerHook} is that it's using {@link ITimerStorage} objects + * to save the values. The {@link ITimerStorage} is responsible for the actual data saving, so + * different strategies can be chosen from (set through the configuration file). + * + * @author Patrice Bouillet + * + */ +public class TimerHook implements IMethodHook, IConstructorHook { + + /** + * The logger of this class. Initialized manually. + */ + private static final Logger LOG = LoggerFactory.getLogger(TimerHook.class); + + /** + * The stack containing the start time values. + */ + private final ThreadLocalStack timeStack = new ThreadLocalStack(); + + /** + * The timer used for accurate measuring. + */ + private final Timer timer; + + /** + * The ID manager. + */ + private final IIdManager idManager; + + /** + * The property accessor. + */ + private final IPropertyAccessor propertyAccessor; + + /** + * The timer storage factory which returns a new {@link ITimerStorage} object every time we + * request one. The returned storage depends on the settings in the configuration file. + */ + private final TimerStorageFactory timerStorageFactory = TimerStorageFactory.getFactory(); + + /** + * The StringConstraint to ensure a maximum length of strings. + */ + private StringConstraint strConstraint; + + /** + * The thread MX bean. + */ + private ThreadMXBean threadMXBean; + + /** + * Defines if the thread CPU time is supported. + */ + private boolean supported = false; + + /** + * Defines if the thread CPU time is enabled. + */ + private boolean enabled = false; + + /** + * The stack containing the start time values. + */ + private final ThreadLocalStack threadCpuTimeStack = new ThreadLocalStack(); + + /** + * The only constructor which needs the used {@link ICoreService} implementation and the used + * {@link Timer}. + * + * @param timer + * The timer. + * @param idManager + * The ID manager. + * @param propertyAccessor + * The property accessor. + * @param param + * Additional parameters passed to the {@link TimerStorageFactory} for proper + * initialization. + * @param threadMXBean + * The bean used to access the cpu time. + */ + public TimerHook(Timer timer, IIdManager idManager, IPropertyAccessor propertyAccessor, Map param, ThreadMXBean threadMXBean) { + this.timer = timer; + this.idManager = idManager; + this.propertyAccessor = propertyAccessor; + this.threadMXBean = threadMXBean; + + try { + // if it is even supported by this JVM + supported = threadMXBean.isThreadCpuTimeSupported(); + if (supported) { + // check if its enabled + enabled = threadMXBean.isThreadCpuTimeEnabled(); + if (!enabled) { + // try to enable it + threadMXBean.setThreadCpuTimeEnabled(true); + // check again now if it is enabled now + enabled = threadMXBean.isThreadCpuTimeEnabled(); + } + } + } catch (RuntimeException e) { + // catching the runtime exceptions which could be thrown by the + // above statements. + LOG.warn("Exception in the TimerHook.", e); + } + + timerStorageFactory.setParameters(param); + this.strConstraint = new StringConstraint(param); + } + + /** + * {@inheritDoc} + */ + public void beforeBody(long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc) { + timeStack.push(new Double(timer.getCurrentTime())); + if (enabled) { + threadCpuTimeStack.push(Long.valueOf(threadMXBean.getCurrentThreadCpuTime())); + } + } + + /** + * {@inheritDoc} + */ + public void firstAfterBody(long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) { + timeStack.push(new Double(timer.getCurrentTime())); + if (enabled) { + threadCpuTimeStack.push(Long.valueOf(threadMXBean.getCurrentThreadCpuTime())); + } + } + + /** + * {@inheritDoc} + */ + public void secondAfterBody(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) { + double endTime = timeStack.pop().doubleValue(); + double startTime = timeStack.pop().doubleValue(); + double duration = endTime - startTime; + + // default setting to a negative number + double cpuDuration = -1.0d; + if (enabled) { + long cpuEndTime = threadCpuTimeStack.pop().longValue(); + long cpuStartTime = threadCpuTimeStack.pop().longValue(); + cpuDuration = (cpuEndTime - cpuStartTime) / 1000000.0d; + } + + List parameterContentData = null; + String prefix = null; + // check if some properties need to be accessed and saved + if (rsc.isPropertyAccess()) { + parameterContentData = propertyAccessor.getParameterContentData(rsc.getPropertyAccessorList(), object, parameters, result); + prefix = parameterContentData.toString(); + + // crop the content strings of all ParameterContentData but leave the prefix as it is + for (ParameterContentData contentData : parameterContentData) { + contentData.setContent(strConstraint.crop(contentData.getContent())); + } + } + + ITimerStorage storage = (ITimerStorage) coreService.getObjectStorage(sensorTypeId, methodId, prefix); + + if (null == storage) { + try { + long platformId = idManager.getPlatformId(); + long registeredSensorTypeId = idManager.getRegisteredSensorTypeId(sensorTypeId); + long registeredMethodId = idManager.getRegisteredMethodId(methodId); + + Timestamp timestamp = new Timestamp(System.currentTimeMillis() - Math.round(duration)); + + boolean charting = "true".equals(rsc.getSettings().get("charting")); + + storage = timerStorageFactory.newStorage(timestamp, platformId, registeredSensorTypeId, registeredMethodId, parameterContentData, charting); + storage.addData(duration, cpuDuration); + + coreService.addObjectStorage(sensorTypeId, methodId, prefix, storage); + } catch (IdNotAvailableException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Could not save the timer data because of an unavailable id. " + e.getMessage()); + } + } + } else { + storage.addData(duration, cpuDuration); + } + } + + /** + * {@inheritDoc} + */ + public void beforeConstructor(long methodId, long sensorTypeId, Object[] parameters, RegisteredSensorConfig rsc) { + timeStack.push(new Double(timer.getCurrentTime())); + if (enabled) { + threadCpuTimeStack.push(Long.valueOf(threadMXBean.getCurrentThreadCpuTime())); + } + } + + /** + * {@inheritDoc} + */ + public void afterConstructor(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc) { + timeStack.push(new Double(timer.getCurrentTime())); + if (enabled) { + threadCpuTimeStack.push(Long.valueOf(threadMXBean.getCurrentThreadCpuTime())); + } + // just call the second after body method directly + secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, null, rsc); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/TimerSensor.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/TimerSensor.java new file mode 100644 index 000000000..1129a7549 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/TimerSensor.java @@ -0,0 +1,82 @@ +package info.novatec.inspectit.agent.sensor.method.timer; + +import info.novatec.inspectit.agent.config.IPropertyAccessor; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.hooking.IHook; +import info.novatec.inspectit.agent.sensor.method.AbstractMethodSensor; +import info.novatec.inspectit.agent.sensor.method.IMethodSensor; +import info.novatec.inspectit.util.Timer; + +import java.lang.management.ManagementFactory; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; + +/** + * The timer sensor which initializes and returns the {@link TimerHook} class. + * + * @author Patrice Bouillet + * + */ +public class TimerSensor extends AbstractMethodSensor implements IMethodSensor { + + /** + * The timer used for accurate measuring. + */ + @Autowired + private Timer timer; + + /** + * The ID manager. + */ + @Autowired + private IIdManager idManager; + + /** + * The property accessor. + */ + @Autowired + private IPropertyAccessor propertyAccessor; + + /** + * The used timer hook. + */ + private TimerHook timerHook = null; + + /** + * No-arg constructor needed for Spring. + */ + public TimerSensor() { + } + + /** + * The default constructor which needs 3 parameter for initialization. + * + * @param timer + * The timer used for accurate measuring. + * @param idManager + * The ID manager. + * @param propertyAccessor + * The property accessor. + */ + public TimerSensor(Timer timer, IIdManager idManager, IPropertyAccessor propertyAccessor) { + this.timer = timer; + this.idManager = idManager; + this.propertyAccessor = propertyAccessor; + } + + /** + * {@inheritDoc} + */ + public IHook getHook() { + return timerHook; + } + + /** + * {@inheritDoc} + */ + public void init(Map parameter) { + timerHook = new TimerHook(timer, idManager, propertyAccessor, parameter, ManagementFactory.getThreadMXBean()); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/TimerStorageFactory.java b/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/TimerStorageFactory.java new file mode 100644 index 000000000..9f689a2f1 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/method/timer/TimerStorageFactory.java @@ -0,0 +1,135 @@ +package info.novatec.inspectit.agent.sensor.method.timer; + +import info.novatec.inspectit.communication.data.ParameterContentData; + +import java.sql.Timestamp; +import java.util.List; +import java.util.Map; + +/** + * Factory for creating storage objects for the Timer sensor according to the definition in the + * configuration. + * + * @author Patrice Bouillet + * + */ +public final class TimerStorageFactory { + + /** + * The singleton of this class. + */ + private static TimerStorageFactory singleton; + + /** + * Raw data transmission mode. + */ + public static final int RAW_DATA_TRANSMISSION = 0; + + /** + * Aggregate the data before sending. + */ + public static final int AGGREGATE_BEFORE_SEND = 1; + + /** + * Optimized mode. + */ + public static final int OPTIMIZED = 2; + + /** + * The default mode. + */ + private int mode = OPTIMIZED; + + /** + * Constructor is private to prevent subclasses and new instances. + */ + private TimerStorageFactory() { + } + + /** + * Multiple instances of a factory aren't needed, so return a singleton of this class. + * + * @return The singleton. + */ + public static TimerStorageFactory getFactory() { + if (null == singleton) { + createTimerStorageFactory(); + } + return singleton; + } + + /** + * Creates singleton in synchronized method. + */ + private static synchronized void createTimerStorageFactory() { + if (null == singleton) { + singleton = new TimerStorageFactory(); + } + } + + /** + * If given {@link Map} contains a key named mode, it is checked against the keywords + * raw, aggregate and optimized. + * + * @param parameters + * The parameters. + */ + public void setParameters(final Map parameters) { + String mode = (String) parameters.get("mode"); + + if (null != mode) { + if ("raw".equals(mode)) { + setMode(RAW_DATA_TRANSMISSION); + } else if ("aggregate".equals(mode)) { + setMode(AGGREGATE_BEFORE_SEND); + } else if ("optimized".equals(mode)) { + setMode(OPTIMIZED); + } + } + } + + /** + * Sets the mode for this factory. It can be one of the following:
+ * RAW_DATA_TRANSMISSION
+ * AGGREGATE_BEFORE_SEND
+ * OPTIMIZED + * + * @param mode + * The mode to set. + */ + public void setMode(final int mode) { + this.mode = mode; + } + + /** + * Returns a new implementation of the {@link ITimerStorage} interface. Depends on the current + * mode which is set through {@link #setMode(int)}. + * + * @param timeStamp + * The time stamp. + * @param platformIdent + * The id of the current platform. + * @param sensorTypeIdent + * The id of the sensor type. + * @param methodIdent + * The id of the method. + * @param parameterContentData + * The contents of some additional parameters. Can be null. + * @param charting + * If TimerData's charting should be set or not. + * @return A new {@link ITimerStorage} implementation object. + */ + public ITimerStorage newStorage(Timestamp timeStamp, long platformIdent, long sensorTypeIdent, long methodIdent, List parameterContentData, boolean charting) { + switch (mode) { + case RAW_DATA_TRANSMISSION: + return new PlainTimerStorage(timeStamp, platformIdent, sensorTypeIdent, methodIdent, parameterContentData, charting); + case AGGREGATE_BEFORE_SEND: + return new AggregateTimerStorage(timeStamp, platformIdent, sensorTypeIdent, methodIdent, parameterContentData, charting); + case OPTIMIZED: + return new OptimizedTimerStorage(timeStamp, platformIdent, sensorTypeIdent, methodIdent, parameterContentData, charting); + default: + return new OptimizedTimerStorage(timeStamp, platformIdent, sensorTypeIdent, methodIdent, parameterContentData, charting); + } + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/AbstractPlatformSensor.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/AbstractPlatformSensor.java new file mode 100644 index 000000000..84246e690 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/AbstractPlatformSensor.java @@ -0,0 +1,37 @@ +package info.novatec.inspectit.agent.sensor.platform; + +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.config.impl.PlatformSensorTypeConfig; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Abstract class for all {@link IPlatformSensor}s to properly initialize after Spring has set all + * the properties. + * + * @author Ivan Senic + * + */ +public abstract class AbstractPlatformSensor implements IPlatformSensor, InitializingBean { + + /** + * Configuration storage for initializing the sensor and registering with the config. + */ + @Autowired + private IConfigurationStorage configurationStorage; + + /** + * {@inheritDoc} + */ + public void afterPropertiesSet() throws Exception { + for (PlatformSensorTypeConfig config : configurationStorage.getPlatformSensorTypes()) { + if (config.getClassName().equals(this.getClass().getName())) { + this.init(config.getParameters()); + config.setSensorType(this); + break; + } + } + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/ClassLoadingInformation.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/ClassLoadingInformation.java new file mode 100644 index 000000000..d2b3da2a1 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/ClassLoadingInformation.java @@ -0,0 +1,168 @@ +package info.novatec.inspectit.agent.sensor.platform; + +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.sensor.platform.provider.RuntimeInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.factory.PlatformSensorInfoProviderFactory; +import info.novatec.inspectit.communication.data.ClassLoadingInformationData; +import info.novatec.inspectit.spring.logger.Log; + +import java.sql.Timestamp; +import java.util.GregorianCalendar; +import java.util.Map; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * This class provides dynamic information about the class loading system through MXBeans. + * + * @author Eduard Tudenhoefner + * + */ +public class ClassLoadingInformation extends AbstractPlatformSensor implements IPlatformSensor { + + /** + * The logger of the class. + */ + @Log + Logger log; + + /** + * The ID Manager used to get the correct IDs. + */ + @Autowired + private IIdManager idManager; + + /** + * The {@link RuntimeInfoProvider} used to retrieve information from the class loading system. + */ + private RuntimeInfoProvider runtimeBean = PlatformSensorInfoProviderFactory.getPlatformSensorInfoProvider().getRuntimeInfoProvider(); + + /** + * No-arg constructor needed for Spring. + */ + public ClassLoadingInformation() { + } + + /** + * The default constructor which needs one parameter. + * + * @param idManager + * The ID Manager. + */ + public ClassLoadingInformation(IIdManager idManager) { + this.idManager = idManager; + } + + /** + * Returns the number of loaded classes in the virtual machine. + * + * @return The number of loaded classes. + */ + public int getLoadedClassCount() { + return runtimeBean.getLoadedClassCount(); + } + + /** + * Returns the total number of loaded classes since the virtual machine started. + * + * @return The total number of loaded classes. + */ + public long getTotalLoadedClassCount() { + return runtimeBean.getTotalLoadedClassCount(); + } + + /** + * Returns the number of unloaded classes since the virtual machine started. + * + * @return The number of unloaded classes. + */ + public long getUnloadedClassCount() { + return runtimeBean.getUnloadedClassCount(); + } + + /** + * Updates all dynamic class loading information. + * + * @param coreService + * The {@link ICoreService}. + * + * @param sensorTypeIdent + * The sensorTypeIdent. + */ + public void update(ICoreService coreService, long sensorTypeIdent) { + int loadedClassCount = this.getLoadedClassCount(); + long totalLoadedClassCount = this.getTotalLoadedClassCount(); + long unloadedClassCount = this.getUnloadedClassCount(); + + ClassLoadingInformationData classLoadingData = (ClassLoadingInformationData) coreService.getPlatformSensorData(sensorTypeIdent); + + if (classLoadingData == null) { + try { + long platformId = idManager.getPlatformId(); + long registeredSensorTypeId = idManager.getRegisteredSensorTypeId(sensorTypeIdent); + Timestamp timestamp = new Timestamp(GregorianCalendar.getInstance().getTimeInMillis()); + + classLoadingData = new ClassLoadingInformationData(timestamp, platformId, registeredSensorTypeId); + classLoadingData.incrementCount(); + + classLoadingData.addLoadedClassCount(loadedClassCount); + classLoadingData.setMinLoadedClassCount(loadedClassCount); + classLoadingData.setMaxLoadedClassCount(loadedClassCount); + + classLoadingData.addTotalLoadedClassCount(totalLoadedClassCount); + classLoadingData.setMinTotalLoadedClassCount(totalLoadedClassCount); + classLoadingData.setMaxTotalLoadedClassCount(totalLoadedClassCount); + + classLoadingData.addUnloadedClassCount(unloadedClassCount); + classLoadingData.setMinUnloadedClassCount(unloadedClassCount); + classLoadingData.setMaxUnloadedClassCount(unloadedClassCount); + + coreService.addPlatformSensorData(sensorTypeIdent, classLoadingData); + } catch (IdNotAvailableException e) { + if (log.isDebugEnabled()) { + log.debug("Could not save the class loading information because of an unavailable id. " + e.getMessage()); + } + } + } else { + classLoadingData.incrementCount(); + classLoadingData.addLoadedClassCount(loadedClassCount); + classLoadingData.addTotalLoadedClassCount(totalLoadedClassCount); + classLoadingData.addUnloadedClassCount(unloadedClassCount); + + if (loadedClassCount < classLoadingData.getMinLoadedClassCount()) { + classLoadingData.setMinLoadedClassCount(loadedClassCount); + } else if (loadedClassCount > classLoadingData.getMaxLoadedClassCount()) { + classLoadingData.setMaxLoadedClassCount(loadedClassCount); + } + + if (totalLoadedClassCount < classLoadingData.getMinTotalLoadedClassCount()) { + classLoadingData.setMinTotalLoadedClassCount(totalLoadedClassCount); + } else if (totalLoadedClassCount > classLoadingData.getMaxTotalLoadedClassCount()) { + classLoadingData.setMaxTotalLoadedClassCount(totalLoadedClassCount); + } + + if (unloadedClassCount < classLoadingData.getMinUnloadedClassCount()) { + classLoadingData.setMinUnloadedClassCount(unloadedClassCount); + } else if (unloadedClassCount > classLoadingData.getMaxUnloadedClassCount()) { + classLoadingData.setMaxUnloadedClassCount(unloadedClassCount); + } + } + } + + /** + * {@inheritDoc} + */ + public void init(Map parameter) { + } + + /** + * {@inheritDoc} + */ + public boolean automaticUpdate() { + return true; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/CompilationInformation.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/CompilationInformation.java new file mode 100644 index 000000000..e53a47ee8 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/CompilationInformation.java @@ -0,0 +1,126 @@ +package info.novatec.inspectit.agent.sensor.platform; + +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.sensor.platform.provider.RuntimeInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.factory.PlatformSensorInfoProviderFactory; +import info.novatec.inspectit.communication.data.CompilationInformationData; +import info.novatec.inspectit.spring.logger.Log; + +import java.sql.Timestamp; +import java.util.GregorianCalendar; +import java.util.Map; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * This class provides dynamic information about the compilation system through MXBeans. + * + * @author Eduard Tudenhoefner + * + */ +public class CompilationInformation extends AbstractPlatformSensor implements IPlatformSensor { + + /** + * The logger of the class. + */ + @Log + Logger log; + + /** + * The ID Manager used to get the correct IDs. + */ + @Autowired + private IIdManager idManager; + + /** + * The {@link RuntimeInfoProvider} used to retrieve information from the compilation system. + */ + private RuntimeInfoProvider runtimeBean = PlatformSensorInfoProviderFactory.getPlatformSensorInfoProvider().getRuntimeInfoProvider(); + + /** + * No-arg constructor needed for Spring. + */ + public CompilationInformation() { + } + + /** + * The default constructor which needs one parameter. + * + * @param idManager + * The ID Manager. + */ + public CompilationInformation(IIdManager idManager) { + this.idManager = idManager; + } + + /** + * Returns the approximate accumulated elapsed time (milliseconds) spent in compilation. + * + * @return The compilation time in milliseconds. + */ + public long getTotalCompilationTime() { + return runtimeBean.getTotalCompilationTime(); + } + + /** + * Updates all dynamic compilation information. + * + * @param coreService + * The {@link ICoreService}. + * + * @param sensorTypeIdent + * The sensorTypeIdent. + */ + public void update(ICoreService coreService, long sensorTypeIdent) { + long totalCompilationTime = this.getTotalCompilationTime(); + + CompilationInformationData compilationData = (CompilationInformationData) coreService.getPlatformSensorData(sensorTypeIdent); + + if (compilationData == null) { + try { + long platformId = idManager.getPlatformId(); + long registeredSensorTypeId = idManager.getRegisteredSensorTypeId(sensorTypeIdent); + Timestamp timestamp = new Timestamp(GregorianCalendar.getInstance().getTimeInMillis()); + + compilationData = new CompilationInformationData(timestamp, platformId, registeredSensorTypeId); + compilationData.incrementCount(); + + compilationData.addTotalCompilationTime(totalCompilationTime); + compilationData.setMinTotalCompilationTime(totalCompilationTime); + compilationData.setMaxTotalCompilationTime(totalCompilationTime); + + coreService.addPlatformSensorData(sensorTypeIdent, compilationData); + } catch (IdNotAvailableException e) { + if (log.isDebugEnabled()) { + log.debug("Could not save the compilation information because of an unavailable id. " + e.getMessage()); + } + } + } else { + compilationData.incrementCount(); + compilationData.addTotalCompilationTime(totalCompilationTime); + + if (totalCompilationTime < compilationData.getMinTotalCompilationTime()) { + compilationData.setMinTotalCompilationTime(totalCompilationTime); + } else if (totalCompilationTime > compilationData.getMaxTotalCompilationTime()) { + compilationData.setMaxTotalCompilationTime(totalCompilationTime); + } + } + } + + /** + * {@inheritDoc} + */ + public void init(Map parameter) { + } + + /** + * {@inheritDoc} + */ + public boolean automaticUpdate() { + return true; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/CpuInformation.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/CpuInformation.java new file mode 100644 index 000000000..d1c98c9f9 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/CpuInformation.java @@ -0,0 +1,131 @@ +package info.novatec.inspectit.agent.sensor.platform; + +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.sensor.platform.provider.OperatingSystemInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.factory.PlatformSensorInfoProviderFactory; +import info.novatec.inspectit.communication.data.CpuInformationData; +import info.novatec.inspectit.spring.logger.Log; + +import java.sql.Timestamp; +import java.util.GregorianCalendar; +import java.util.Map; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * This class provides dynamic information about the underlying operating system through MXBeans. + * + * @author Eduard Tudenhoefner + * + */ +public class CpuInformation extends AbstractPlatformSensor implements IPlatformSensor { + + /** + * The logger of the class. + */ + @Log + Logger log; + + /** + * The ID Manager used to get the correct IDs. + */ + @Autowired + private IIdManager idManager; + + /** + * The {@link OperatingSystemInfoProvider} used to retrieve information from the operating + * system. + */ + private OperatingSystemInfoProvider osBean = PlatformSensorInfoProviderFactory.getPlatformSensorInfoProvider().getOperatingSystemInfoProvider(); + + /** + * No-arg constructor needed for Spring. + */ + public CpuInformation() { + } + + /** + * The default constructor which needs one parameter. + * + * @param idManager + * The ID Manager. + */ + public CpuInformation(IIdManager idManager) { + this.idManager = idManager; + } + + /** + * Returns the process cpu time. + * + * @return the process cpu time. + */ + public long getProcessCpuTime() { + return osBean.getProcessCpuTime(); + } + + /** + * Updates all dynamic cpu information. + * + * @param coreService + * The {@link ICoreService}. + * + * @param sensorTypeIdent + * The sensorTypeIdent. + */ + public void update(ICoreService coreService, long sensorTypeIdent) { + long processCpuTime = this.getProcessCpuTime(); + float cpuUsage = osBean.retrieveCpuUsage(); + + CpuInformationData osData = (CpuInformationData) coreService.getPlatformSensorData(sensorTypeIdent); + + if (osData == null) { + try { + long platformId = idManager.getPlatformId(); + long registeredSensorTypeId = idManager.getRegisteredSensorTypeId(sensorTypeIdent); + Timestamp timestamp = new Timestamp(GregorianCalendar.getInstance().getTimeInMillis()); + + osData = new CpuInformationData(timestamp, platformId, registeredSensorTypeId); + osData.incrementCount(); + + osData.updateProcessCpuTime(processCpuTime); + + osData.addCpuUsage(cpuUsage); + osData.setMinCpuUsage(cpuUsage); + osData.setMaxCpuUsage(cpuUsage); + + coreService.addPlatformSensorData(sensorTypeIdent, osData); + } catch (IdNotAvailableException e) { + if (log.isDebugEnabled()) { + log.debug("Could not save the cpu information because of an unavailable id. " + e.getMessage()); + } + } + } else { + osData.incrementCount(); + osData.updateProcessCpuTime(processCpuTime); + osData.addCpuUsage(cpuUsage); + + if (cpuUsage < osData.getMinCpuUsage()) { + osData.setMinCpuUsage(cpuUsage); + } else if (cpuUsage > osData.getMaxCpuUsage()) { + osData.setMaxCpuUsage(cpuUsage); + } + } + + } + + /** + * {@inheritDoc} + */ + public void init(Map parameter) { + } + + /** + * {@inheritDoc} + */ + public boolean automaticUpdate() { + return true; + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/IPlatformSensor.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/IPlatformSensor.java new file mode 100644 index 000000000..e4ef39bde --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/IPlatformSensor.java @@ -0,0 +1,34 @@ +package info.novatec.inspectit.agent.sensor.platform; + +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.sensor.ISensor; + +/** + * This interface is implemented by classes which provide information about the system, like CPU, + * Memory etc. + * + * @author Patrice Bouillet + * + */ +public interface IPlatformSensor extends ISensor { + + /** + * Defines if the sensor should be updated automatically. For static information, this method + * shall return false as its data will nearly never change. Thus a decrease in + * network traffic and processing usage can be accomplished. + * + * @return If this platform sensor should be updated automatically. + */ + boolean automaticUpdate(); + + /** + * This method is called whenever the sensor should be updated. + * + * @param coreService + * The core service which is needed to store the measurements to. + * @param sensorTypeIdent + * The ID of the sensor type so that old data can be found. (for aggregating etc.) + */ + void update(ICoreService coreService, long sensorTypeIdent); + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/MemoryInformation.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/MemoryInformation.java new file mode 100644 index 000000000..bd2b65b43 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/MemoryInformation.java @@ -0,0 +1,261 @@ +package info.novatec.inspectit.agent.sensor.platform; + +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.sensor.platform.provider.MemoryInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.OperatingSystemInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.factory.PlatformSensorInfoProviderFactory; +import info.novatec.inspectit.communication.data.MemoryInformationData; +import info.novatec.inspectit.spring.logger.Log; + +import java.sql.Timestamp; +import java.util.GregorianCalendar; +import java.util.Map; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * This class provides dynamic information about the memory system through MXBeans. + * + * @author Eduard Tudenhoefner + * + */ +public class MemoryInformation extends AbstractPlatformSensor implements IPlatformSensor { + + /** + * The logger of the class. + */ + @Log + Logger log; + + /** + * The ID Manager used to get the correct IDs. + */ + @Autowired + private IIdManager idManager; + + /** + * The {@link MemoryInfoProvider} used to retrieve heap memory information. + */ + private MemoryInfoProvider memoryBean = PlatformSensorInfoProviderFactory.getPlatformSensorInfoProvider().getMemoryInfoProvider(); + + /** + * The {@link OperatingSystemInfoProvider} used to retrieve physical memory information. + */ + private OperatingSystemInfoProvider osBean = PlatformSensorInfoProviderFactory.getPlatformSensorInfoProvider().getOperatingSystemInfoProvider(); + + /** + * No-arg constructor needed for Spring. + */ + public MemoryInformation() { + } + + /** + * The default constructor which needs one parameter. + * + * @param idManager + * The ID Manager. + */ + public MemoryInformation(IIdManager idManager) { + this.idManager = idManager; + } + + /** + * Returns the amount of free physical memory. + * + * @return the free physical memory. + */ + public long getFreePhysMemory() { + return osBean.getFreePhysicalMemorySize(); + } + + /** + * Returns the amount of free swap space. + * + * @return the free swap space. + */ + public long getFreeSwapSpace() { + return osBean.getFreeSwapSpaceSize(); + } + + /** + * Returns the amount of virtual memory that is guaranteed to be available to the running + * process. + * + * @return the virtual memory size. + */ + public long getComittedVirtualMemSize() { + return osBean.getCommittedVirtualMemorySize(); + } + + /** + * Returns the memory usage of the heap that is used for object allocation. + * + * @return the memory usage of the heap for object allocation. + */ + public long getUsedHeapMemorySize() { + return memoryBean.getHeapMemoryUsage().getUsed(); + } + + /** + * Returns the amount of memory that is guaranteed to be available for use by the virtual + * machine for heap memory usage. + * + * @return the amount of guaranteed to be available memory for heap memory usage. + */ + public long getComittedHeapMemorySize() { + return memoryBean.getHeapMemoryUsage().getCommitted(); + } + + /** + * Returns the amount of memory for non-heap memory usage of the virtual machine. + * + * @return the amount of memory for non-heap memory usage. + */ + public long getUsedNonHeapMemorySize() { + return memoryBean.getNonHeapMemoryUsage().getUsed(); + } + + /** + * Returns the amount of memory that is guaranteed to be available for use by the virtual + * machine for non-heap memory usage. + * + * @return the guaranteed to be available memory for non-heap memory usage. + */ + public long getComittedNonHeapMemoryUsage() { + return memoryBean.getNonHeapMemoryUsage().getCommitted(); + } + + /** + * Updates all dynamic memory informations. + * + * @param coreService + * The {@link ICoreService}. + * + * @param sensorTypeIdent + * The sensorTypeIdent. + */ + public void update(ICoreService coreService, long sensorTypeIdent) { + long freePhysMemory = this.getFreePhysMemory(); + long freeSwapSpace = this.getFreeSwapSpace(); + long comittedVirtualMemSize = this.getComittedVirtualMemSize(); + long usedHeapMemorySize = this.getUsedHeapMemorySize(); + long comittedHeapMemorySize = this.getComittedHeapMemorySize(); + long usedNonHeapMemorySize = this.getUsedNonHeapMemorySize(); + long comittedNonHeapMemorySize = this.getComittedNonHeapMemoryUsage(); + + MemoryInformationData memoryData = (MemoryInformationData) coreService.getPlatformSensorData(sensorTypeIdent); + + if (memoryData == null) { + try { + long platformId = idManager.getPlatformId(); + long registeredSensorTypeId = idManager.getRegisteredSensorTypeId(sensorTypeIdent); + Timestamp timestamp = new Timestamp(GregorianCalendar.getInstance().getTimeInMillis()); + + memoryData = new MemoryInformationData(timestamp, platformId, registeredSensorTypeId); + memoryData.incrementCount(); + + memoryData.addFreePhysMemory(freePhysMemory); + memoryData.setMinFreePhysMemory(freePhysMemory); + memoryData.setMaxFreePhysMemory(freePhysMemory); + + memoryData.addFreeSwapSpace(freeSwapSpace); + memoryData.setMinFreeSwapSpace(freeSwapSpace); + memoryData.setMaxFreeSwapSpace(freeSwapSpace); + + memoryData.addComittedVirtualMemSize(comittedVirtualMemSize); + memoryData.setMinComittedVirtualMemSize(comittedVirtualMemSize); + memoryData.setMaxComittedVirtualMemSize(comittedVirtualMemSize); + + memoryData.addUsedHeapMemorySize(usedHeapMemorySize); + memoryData.setMinUsedHeapMemorySize(usedHeapMemorySize); + memoryData.setMaxUsedHeapMemorySize(usedHeapMemorySize); + + memoryData.addComittedHeapMemorySize(comittedHeapMemorySize); + memoryData.setMinComittedHeapMemorySize(comittedHeapMemorySize); + memoryData.setMaxComittedHeapMemorySize(comittedHeapMemorySize); + + memoryData.addUsedNonHeapMemorySize(usedNonHeapMemorySize); + memoryData.setMinUsedNonHeapMemorySize(usedNonHeapMemorySize); + memoryData.setMaxUsedNonHeapMemorySize(usedNonHeapMemorySize); + + memoryData.addComittedNonHeapMemorySize(comittedNonHeapMemorySize); + memoryData.setMinComittedNonHeapMemorySize(comittedNonHeapMemorySize); + memoryData.setMaxComittedNonHeapMemorySize(comittedNonHeapMemorySize); + + coreService.addPlatformSensorData(sensorTypeIdent, memoryData); + } catch (IdNotAvailableException e) { + if (log.isDebugEnabled()) { + log.debug("Could not save the memory information because of an unavailable id. " + e.getMessage()); + } + } + } else { + memoryData.incrementCount(); + memoryData.addFreePhysMemory(freePhysMemory); + memoryData.addFreeSwapSpace(freeSwapSpace); + memoryData.addComittedVirtualMemSize(comittedVirtualMemSize); + memoryData.addUsedHeapMemorySize(usedHeapMemorySize); + memoryData.addComittedHeapMemorySize(comittedHeapMemorySize); + memoryData.addUsedNonHeapMemorySize(usedNonHeapMemorySize); + memoryData.addComittedNonHeapMemorySize(comittedNonHeapMemorySize); + + if (freePhysMemory < memoryData.getMinFreePhysMemory()) { + memoryData.setMinFreePhysMemory(freePhysMemory); + } else if (freePhysMemory > memoryData.getMaxFreePhysMemory()) { + memoryData.setMaxFreePhysMemory(freePhysMemory); + } + + if (freeSwapSpace < memoryData.getMinFreeSwapSpace()) { + memoryData.setMinFreeSwapSpace(freeSwapSpace); + } else if (freeSwapSpace > memoryData.getMaxFreeSwapSpace()) { + memoryData.setMaxFreeSwapSpace(freeSwapSpace); + } + + if (comittedVirtualMemSize < memoryData.getMinComittedVirtualMemSize()) { + memoryData.setMinComittedVirtualMemSize(comittedVirtualMemSize); + } else if (comittedVirtualMemSize > memoryData.getMaxComittedVirtualMemSize()) { + memoryData.setMaxComittedVirtualMemSize(comittedVirtualMemSize); + } + + if (usedHeapMemorySize < memoryData.getMinUsedHeapMemorySize()) { + memoryData.setMinUsedHeapMemorySize(usedHeapMemorySize); + } else if (usedHeapMemorySize > memoryData.getMaxUsedHeapMemorySize()) { + memoryData.setMaxUsedHeapMemorySize(usedHeapMemorySize); + } + + if (comittedHeapMemorySize < memoryData.getMinComittedHeapMemorySize()) { + memoryData.setMinComittedHeapMemorySize(comittedHeapMemorySize); + } else if (comittedHeapMemorySize > memoryData.getMaxComittedHeapMemorySize()) { + memoryData.setMaxComittedHeapMemorySize(comittedHeapMemorySize); + } + + if (usedNonHeapMemorySize < memoryData.getMinUsedNonHeapMemorySize()) { + memoryData.setMinUsedNonHeapMemorySize(usedNonHeapMemorySize); + } else if (usedNonHeapMemorySize > memoryData.getMaxUsedNonHeapMemorySize()) { + memoryData.setMaxUsedNonHeapMemorySize(usedNonHeapMemorySize); + } + + if (comittedNonHeapMemorySize < memoryData.getMinComittedNonHeapMemorySize()) { + memoryData.setMinComittedNonHeapMemorySize(comittedNonHeapMemorySize); + } else if (comittedNonHeapMemorySize > memoryData.getMaxComittedNonHeapMemorySize()) { + memoryData.setMaxComittedNonHeapMemorySize(comittedNonHeapMemorySize); + } + } + } + + /** + * {@inheritDoc} + */ + public void init(Map parameter) { + } + + /** + * {@inheritDoc} + */ + public boolean automaticUpdate() { + return true; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/RuntimeInformation.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/RuntimeInformation.java new file mode 100644 index 000000000..d38752ffd --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/RuntimeInformation.java @@ -0,0 +1,126 @@ +package info.novatec.inspectit.agent.sensor.platform; + +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.sensor.platform.provider.RuntimeInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.factory.PlatformSensorInfoProviderFactory; +import info.novatec.inspectit.communication.data.RuntimeInformationData; +import info.novatec.inspectit.spring.logger.Log; + +import java.sql.Timestamp; +import java.util.GregorianCalendar; +import java.util.Map; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * This class provides dynamic information about the runtime of the Virtual Machine through MXBeans. + * + * @author Eduard Tudenhoefner + * + */ +public class RuntimeInformation extends AbstractPlatformSensor implements IPlatformSensor { + + /** + * The logger of the class. + */ + @Log + Logger log; + + /** + * The ID Manager used to get the correct IDs. + */ + @Autowired + private IIdManager idManager; + + /** + * The {@link RuntimeInfoProvider} used to retrieve information from the runtime. + */ + private RuntimeInfoProvider runtimeBean = PlatformSensorInfoProviderFactory.getPlatformSensorInfoProvider().getRuntimeInfoProvider(); + + /** + * No-arg constructor needed for Spring. + */ + public RuntimeInformation() { + } + + /** + * The default constructor which needs one parameter. + * + * @param idManager + * The ID Manager. + */ + public RuntimeInformation(IIdManager idManager) { + this.idManager = idManager; + } + + /** + * Returns the uptime of the virtual machine in milliseconds. + * + * @return the uptime in milliseconds. + */ + public long getUptime() { + return runtimeBean.getUptime(); + } + + /** + * Updates all dynamic runtime informations. + * + * @param coreService + * The {@link ICoreService}. + * + * @param sensorTypeIdent + * The sensorTypeIdent. + */ + public void update(ICoreService coreService, long sensorTypeIdent) { + long uptime = this.getUptime(); + + RuntimeInformationData runtimeData = (RuntimeInformationData) coreService.getPlatformSensorData(sensorTypeIdent); + + if (runtimeData == null) { + try { + long platformId = idManager.getPlatformId(); + long registeredSensorTypeId = idManager.getRegisteredSensorTypeId(sensorTypeIdent); + Timestamp timestamp = new Timestamp(GregorianCalendar.getInstance().getTimeInMillis()); + runtimeData = new RuntimeInformationData(timestamp, platformId, registeredSensorTypeId); + + runtimeData.incrementCount(); + runtimeData.addUptime(uptime); + runtimeData.setMinUptime(uptime); + runtimeData.setMaxUptime(uptime); + + coreService.addPlatformSensorData(sensorTypeIdent, runtimeData); + } catch (IdNotAvailableException e) { + if (log.isDebugEnabled()) { + log.debug("Could not save the runtime information because of an unavailable id. " + e.getMessage()); + } + } + } else { + + runtimeData.incrementCount(); + runtimeData.addUptime(uptime); + + if (uptime < runtimeData.getMinUptime()) { + runtimeData.setMinUptime(uptime); + } else if (uptime > runtimeData.getMaxUptime()) { + runtimeData.setMaxUptime(uptime); + } + } + } + + /** + * {@inheritDoc} + */ + public void init(Map parameter) { + } + + /** + * {@inheritDoc} + */ + public boolean automaticUpdate() { + return true; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/SystemInformation.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/SystemInformation.java new file mode 100644 index 000000000..a4ead156d --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/SystemInformation.java @@ -0,0 +1,354 @@ +package info.novatec.inspectit.agent.sensor.platform; + +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.sensor.platform.provider.MemoryInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.OperatingSystemInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.RuntimeInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.factory.PlatformSensorInfoProviderFactory; +import info.novatec.inspectit.communication.data.SystemInformationData; +import info.novatec.inspectit.spring.logger.Log; + +import java.sql.Timestamp; +import java.util.GregorianCalendar; +import java.util.Map; +import java.util.Properties; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * This class provides static information about heap memory/operating system/runtime through + * MXBeans. + * + * @author Eduard Tudenhoefner + * + */ +public class SystemInformation extends AbstractPlatformSensor implements IPlatformSensor { + + /** + * The logger of the class. + */ + @Log + Logger log; + + /** + * The maximum length of the fields saved into the database. + */ + private static final int MAX_LENGTH = 10000; + + /** + * The ID Manager used to get the correct IDs. + */ + @Autowired + private IIdManager idManager; + + /** + * After the first update()-call the static information will only be updated when the update is + * requested by the user. + */ + private boolean updateRequested = true; + + /** + * The {@link OperatingSystemInfoProvider} used to retrieve information from the operating + * system. + */ + private OperatingSystemInfoProvider osBean = PlatformSensorInfoProviderFactory.getPlatformSensorInfoProvider().getOperatingSystemInfoProvider(); + + /** + * The {@link MemoryInfoProvider} used to retrieve heap memory information. + */ + private MemoryInfoProvider memoryBean = PlatformSensorInfoProviderFactory.getPlatformSensorInfoProvider().getMemoryInfoProvider(); + + /** + * The {@link RuntimeInfoProvider} used to retrieve information from the runtime VM. + */ + private RuntimeInfoProvider runtimeBean = PlatformSensorInfoProviderFactory.getPlatformSensorInfoProvider().getRuntimeInfoProvider(); + + /** + * No-arg constructor needed for Spring. + */ + public SystemInformation() { + } + + /** + * The default constructor which needs one parameter. + * + * @param idManager + * The ID Manager. + */ + public SystemInformation(IIdManager idManager) { + this.idManager = idManager; + } + + /** + * Returns the total amount of physical memory. + * + * @return The total amount of physical memory. + */ + public long getTotalPhysMemory() { + return osBean.getTotalPhysicalMemorySize(); + } + + /** + * Returns the total amount of swap space. + * + * @return The total amount of swap space. + */ + public long getTotalSwapSpace() { + return osBean.getTotalSwapSpaceSize(); + } + + /** + * Returns the number of processors available to the virtual machine. + * + * @return The number of processors available to the virtual machine. + */ + public int getAvailableProcessors() { + return osBean.getAvailableProcessors(); + } + + /** + * Returns the operating system architecture. + * + * @return The operating system architecture. + */ + public String getArchitecture() { + return osBean.getArch(); + } + + /** + * Returns the name of the operating system. + * + * @return The name of the operating system. + */ + public String getOsName() { + return osBean.getName(); + } + + /** + * Returns the version of the operating system. + * + * @return The version of the operating system. + */ + public String getOsVersion() { + return osBean.getVersion(); + } + + /** + * Return the name of the Just-in-time (JIT) compiler. + * + * @return The name of the Just-in-time (JIT) compiler. + */ + public String getJitCompilerName() { + return runtimeBean.getJitCompilerName(); + } + + /** + * Returns the java class path that is used by the system class loader to search for class + * files. + * + * @return The java class path that is used by the system class loader to search for class + * files. + */ + public String getClassPath() { + return runtimeBean.getClassPath(); + } + + /** + * Returns the boot class path that is used by the bootstrap class loader to search for class + * files. + * + * @return The boot class path that is used by the bootstrap class loader to search for class + * files. + */ + public String getBootClassPath() { + return runtimeBean.getBootClassPath(); + } + + /** + * Returns the java library path. + * + * @return The java library path. + */ + public String getLibraryPath() { + return runtimeBean.getLibraryPath(); + } + + /** + * Returns the vendor of the virtual machine. + * + * @return The vendor of the virtual machine. + */ + public String getVmVendor() { + return runtimeBean.getVmVendor(); + } + + /** + * Returns the name of the virtual machine. + * + * @return The name of the virtual machine. + */ + public String getVmName() { + return runtimeBean.getVmName(); + } + + /** + * Returns the version of the virtual machine. + * + * @return The version of the virtual machine. + */ + public String getVmVersion() { + return runtimeBean.getVmVersion(); + } + + /** + * Returns the name representing the running virtual machine. for example: 12456@pc-name. + * + * @return The name representing the running virtual machine. + */ + public String getVmSpecName() { + return runtimeBean.getSpecName(); + } + + /** + * Returns the initial amount of memory that the virtual machine requests from the operating + * system for heap memory management during startup. + * + * @return The initial amount of memory that the virtual machine requests from the operating + * system for heap memory management during startup. + */ + public long getInitHeapMemorySize() { + return memoryBean.getHeapMemoryUsage().getInit(); + } + + /** + * Returns the maximum amount of memory that can be used for heap memory management. + * + * @return The maximum amount of memory that can be used for heap memory management. + */ + public long getMaxHeapMemorySize() { + return memoryBean.getHeapMemoryUsage().getMax(); + } + + /** + * Returns the initial amount of memory that the virtual machine requests from the operating + * system for non-heap memory management during startup. + * + * @return The initial amount of memory that the virtual machine requests from the operating + * system for non-heap memory management during startup. + */ + public long getInitNonHeapMemorySize() { + return memoryBean.getNonHeapMemoryUsage().getInit(); + } + + /** + * Returns the maximum amount of memory that can be used for non-heap memory management. + * + * @return The maximum amount of memory that can be used for non-heap memory management. + */ + public long getMaxNonHeapMemorySize() { + return memoryBean.getNonHeapMemoryUsage().getMax(); + } + + /** + * Updates all static information. + * + * @param coreService + * The {@link ICoreService}. + * + * @param sensorTypeIdent + * The sensorTypeIdent. + */ + public void update(ICoreService coreService, long sensorTypeIdent) { + try { + long platformId = idManager.getPlatformId(); + long registeredSensorTypeId = idManager.getRegisteredSensorTypeId(sensorTypeIdent); + Timestamp timestamp = new Timestamp(GregorianCalendar.getInstance().getTimeInMillis()); + + SystemInformationData systemData = new SystemInformationData(timestamp, platformId, registeredSensorTypeId); + + updateRequested = false; + String vmArgumentName = ""; + String vmArgumentValue = ""; + long totalPhysMemory = this.getTotalPhysMemory(); + long totalSwapSpace = this.getTotalSwapSpace(); + int availableProcessors = this.getAvailableProcessors(); + String architecture = this.getArchitecture(); + String osName = this.getOsName(); + String osVersion = this.getOsVersion(); + String jitCompilerName = this.getJitCompilerName(); + String classPath = crop(this.getClassPath()); + String bootClassPath = crop(this.getBootClassPath()); + String libraryPath = crop(this.getLibraryPath()); + String vmVendor = this.getVmVendor(); + String vmVersion = this.getVmVersion(); + String vmName = this.getVmName(); + String vmSpecName = this.getVmSpecName(); + long initHeapMemorySize = this.getInitHeapMemorySize(); + long maxHeapMemorySize = this.getMaxHeapMemorySize(); + long initNonHeapMemorySize = this.getInitNonHeapMemorySize(); + long maxNonHeapMemorySize = this.getMaxNonHeapMemorySize(); + + systemData.setTotalPhysMemory(totalPhysMemory); + systemData.setTotalSwapSpace(totalSwapSpace); + systemData.setAvailableProcessors(availableProcessors); + systemData.setArchitecture(architecture); + systemData.setOsName(osName); + systemData.setOsVersion(osVersion); + systemData.setJitCompilerName(jitCompilerName); + systemData.setClassPath(classPath); + systemData.setBootClassPath(bootClassPath); + systemData.setLibraryPath(libraryPath); + systemData.setVmVendor(vmVendor); + systemData.setVmVersion(vmVersion); + systemData.setVmName(vmName); + systemData.setVmSpecName(vmSpecName); + systemData.setInitHeapMemorySize(initHeapMemorySize); + systemData.setMaxHeapMemorySize(maxHeapMemorySize); + systemData.setInitNonHeapMemorySize(initNonHeapMemorySize); + systemData.setMaxNonHeapMemorySize(maxNonHeapMemorySize); + + Properties properties = System.getProperties(); + for (Map.Entry property : properties.entrySet()) { + vmArgumentName = (String) property.getKey(); + vmArgumentValue = (String) property.getValue(); + systemData.addVMArguments(vmArgumentName, vmArgumentValue); + } + + coreService.addPlatformSensorData(sensorTypeIdent, systemData); + } catch (IdNotAvailableException e) { + if (log.isDebugEnabled()) { + log.debug("Could not save the system information because of an unavailable id. " + e.getMessage()); + } + } + } + + /** + * {@inheritDoc} + */ + public void init(Map parameter) { + } + + /** + * {@inheritDoc} + */ + public boolean automaticUpdate() { + return updateRequested; + } + + /** + * Crops a string if it is longer than the specified MAX_LENGTH. + * + * @param value + * The value to crop. + * @return A cropped string which length is smaller than the MAX_LENGTH. + */ + private String crop(String value) { + if (null != value && value.length() > MAX_LENGTH) { + return value.substring(0, MAX_LENGTH); + } + return value; + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/ThreadInformation.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/ThreadInformation.java new file mode 100644 index 000000000..9afc17118 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/ThreadInformation.java @@ -0,0 +1,189 @@ +package info.novatec.inspectit.agent.sensor.platform; + +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.sensor.platform.provider.ThreadInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.factory.PlatformSensorInfoProviderFactory; +import info.novatec.inspectit.communication.data.ThreadInformationData; +import info.novatec.inspectit.spring.logger.Log; + +import java.sql.Timestamp; +import java.util.GregorianCalendar; +import java.util.Map; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * This class provides dynamic information about the thread system through MXBeans. + * + * @author Eduard Tudenhoefner + * + */ +public class ThreadInformation extends AbstractPlatformSensor implements IPlatformSensor { + + /** + * The logger of the class. + */ + @Log + Logger log; + + /** + * The ID Manager used to get the correct IDs. + */ + @Autowired + private IIdManager idManager; + + /** + * The {@link ThreadInfoProvider} used to retrieve information from the thread system. + */ + private ThreadInfoProvider threadBean = PlatformSensorInfoProviderFactory.getPlatformSensorInfoProvider().getThreadInfoProvider(); + + /** + * No-arg constructor needed for Spring. + */ + public ThreadInformation() { + } + + /** + * The default constructor which needs one parameter. + * + * @param idManager + * The ID Manager. + */ + public ThreadInformation(IIdManager idManager) { + this.idManager = idManager; + } + + /** + * Returns the current number of live daemon threads. + * + * @return The daemon thread count. + */ + public int getDaemonThreadCount() { + return threadBean.getDaemonThreadCount(); + } + + /** + * Returns the peak live thread count since the virtual machine started. + * + * @return The peak thread count. + */ + public int getPeakThreadCount() { + return threadBean.getPeakThreadCount(); + } + + /** + * Returns the live thread count both daemon and non-daemon threads. + * + * @return The thread count. + */ + public int getThreadCount() { + return threadBean.getThreadCount(); + } + + /** + * Returns the total number of created and also started threads. + * + * @return The total started thread count. + */ + public long getTotalStartedThreadCount() { + return threadBean.getTotalStartedThreadCount(); + } + + /** + * Updates all dynamic thread information. + * + * @param coreService + * The {@link ICoreService}. + * + * @param sensorTypeIdent + * The sensorTypeIdent. + */ + public void update(ICoreService coreService, long sensorTypeIdent) { + int daemonThreadCount = this.getDaemonThreadCount(); + int peakThreadCount = this.getPeakThreadCount(); + int threadCount = this.getThreadCount(); + long totalStartedThreadCount = this.getTotalStartedThreadCount(); + + ThreadInformationData threadData = (ThreadInformationData) coreService.getPlatformSensorData(sensorTypeIdent); + + if (threadData == null) { + try { + long platformId = idManager.getPlatformId(); + long registeredSensorTypeId = idManager.getRegisteredSensorTypeId(sensorTypeIdent); + Timestamp timestamp = new Timestamp(GregorianCalendar.getInstance().getTimeInMillis()); + + threadData = new ThreadInformationData(timestamp, platformId, registeredSensorTypeId); + threadData.incrementCount(); + + threadData.addDaemonThreadCount(daemonThreadCount); + threadData.setMinDaemonThreadCount(daemonThreadCount); + threadData.setMaxDaemonThreadCount(daemonThreadCount); + + threadData.addPeakThreadCount(peakThreadCount); + threadData.setMinPeakThreadCount(peakThreadCount); + threadData.setMaxPeakThreadCount(peakThreadCount); + + threadData.addThreadCount(threadCount); + threadData.setMinThreadCount(threadCount); + threadData.setMaxThreadCount(threadCount); + + threadData.addTotalStartedThreadCount(totalStartedThreadCount); + threadData.setMinTotalStartedThreadCount(totalStartedThreadCount); + threadData.setMaxTotalStartedThreadCount(totalStartedThreadCount); + + coreService.addPlatformSensorData(sensorTypeIdent, threadData); + } catch (IdNotAvailableException e) { + if (log.isDebugEnabled()) { + log.debug("Could not save the thread information because of an unavailable id. " + e.getMessage()); + } + } + } else { + threadData.incrementCount(); + threadData.addDaemonThreadCount(daemonThreadCount); + threadData.addPeakThreadCount(peakThreadCount); + threadData.addThreadCount(threadCount); + threadData.addTotalStartedThreadCount(totalStartedThreadCount); + + if (daemonThreadCount < threadData.getMinDaemonThreadCount()) { + threadData.setMinDaemonThreadCount(daemonThreadCount); + } else if (daemonThreadCount > threadData.getMaxDaemonThreadCount()) { + threadData.setMaxDaemonThreadCount(daemonThreadCount); + } + + if (peakThreadCount < threadData.getMinPeakThreadCount()) { + threadData.setMinPeakThreadCount(peakThreadCount); + } else if (peakThreadCount > threadData.getMaxPeakThreadCount()) { + threadData.setMaxPeakThreadCount(peakThreadCount); + } + + if (threadCount < threadData.getMinThreadCount()) { + threadData.setMinThreadCount(threadCount); + } else if (threadCount > threadData.getMaxThreadCount()) { + threadData.setMaxThreadCount(threadCount); + } + + if (totalStartedThreadCount < threadData.getMinTotalStartedThreadCount()) { + threadData.setMinTotalStartedThreadCount(totalStartedThreadCount); + } else if (totalStartedThreadCount > threadData.getMaxTotalStartedThreadCount()) { + threadData.setMaxTotalStartedThreadCount(totalStartedThreadCount); + } + } + } + + /** + * {@inheritDoc} + */ + public void init(Map parameter) { + } + + /** + * {@inheritDoc} + */ + public boolean automaticUpdate() { + return true; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/MemoryInfoProvider.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/MemoryInfoProvider.java new file mode 100644 index 000000000..b1b26fa9c --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/MemoryInfoProvider.java @@ -0,0 +1,38 @@ +package info.novatec.inspectit.agent.sensor.platform.provider; + +import java.lang.management.MemoryUsage; + +/** + * The management interface for the memory system of the Java virtual machine. + * + * @author Eduard Tudenhoefner + * + */ +public interface MemoryInfoProvider { + + /** + * Returns the current memory usage of the heap that is used for object allocation. The heap + * consists of one or more memory pools. The used and committed size of the + * returned memory usage is the sum of those values of all heap memory pools whereas the + * init and max size of the returned memory usage represents the setting of + * the heap memory which may not be the sum of those of all heap memory pools. + *

+ * The amount of used memory in the returned memory usage is the amount of memory occupied by + * both live objects and garbage objects that have not been collected, if any. + * + * @return a {@link MemoryUsage} object representing the heap memory usage. + */ + MemoryUsage getHeapMemoryUsage(); + + /** + * Returns the current memory usage of non-heap memory that is used by the Java virtual machine. + * The non-heap memory consists of one or more memory pools. The used and + * committed size of the returned memory usage is the sum of those values of all + * non-heap memory pools whereas the init and max size of the returned memory + * usage represents the setting of the non-heap memory which may not be the sum of those of all + * non-heap memory pools. + * + * @return a {@link MemoryUsage} object representing the non-heap memory usage. + */ + MemoryUsage getNonHeapMemoryUsage(); +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/OperatingSystemInfoProvider.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/OperatingSystemInfoProvider.java new file mode 100644 index 000000000..81b8df8fd --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/OperatingSystemInfoProvider.java @@ -0,0 +1,74 @@ +package info.novatec.inspectit.agent.sensor.platform.provider; + +/** + * The management interface for the underlying operating system on which the Java virtual machine is + * executed. + * + * @author Eduard Tudenhoefner + * + */ +public interface OperatingSystemInfoProvider { + + /** + * @return The name of the underlying operating system. + */ + String getName(); + + /** + * @return The operating system architecture. + */ + String getArch(); + + /** + * @return The operating system version. + */ + String getVersion(); + + /** + * @return The number of processors available to the Java virtual machine. + */ + int getAvailableProcessors(); + + /** + * @return The amount of virtual memory that is guaranteed to be available to the running + * process in bytes, or -1 if this operation is not supported. + */ + long getCommittedVirtualMemorySize(); + + /** + * @return The amount of free physical memory in bytes. + */ + long getFreePhysicalMemorySize(); + + /** + * @return The amount of free swap space in bytes. + */ + long getFreeSwapSpaceSize(); + + /** + * @return The CPU time used by the process on which the Java virtual machine is running in + * nanoseconds. The returned value is of nanoseconds precision but not necessarily + * nanoseconds accuracy. This method returns -1 if the the platform does not support + * this operation. + */ + long getProcessCpuTime(); + + /** + * @return The total amount of physical memory in bytes. + */ + long getTotalPhysicalMemorySize(); + + /** + * @return The total amount of swap space in bytes. + */ + long getTotalSwapSpaceSize(); + + /** + * @return The current cpu usage for the JVM process. This value is a float in the [0.0,99.0] + * interval. A value of 0.0 means that none of the CPUs were running threads from the + * JVM process during the recent period of time observed, while a value of 99.0 means + * that all CPUs were actively running threads from the JVM 99% of the time during the + * recent period being observed. + */ + float retrieveCpuUsage(); +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/PlatformSensorInfoProvider.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/PlatformSensorInfoProvider.java new file mode 100644 index 000000000..fd0e2c62c --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/PlatformSensorInfoProvider.java @@ -0,0 +1,39 @@ +package info.novatec.inspectit.agent.sensor.platform.provider; + +/** + * Interface for a provider platform sensor infos. + * + * @author Ivan Senic + * + */ +public interface PlatformSensorInfoProvider { + + /** + * Returns the {@link MemoryInfoProvider}. + * + * @return Returns the {@link MemoryInfoProvider}. + */ + MemoryInfoProvider getMemoryInfoProvider(); + + /** + * Returns the {@link OperatingSystemInfoProvider}. + * + * @return Returns the {@link OperatingSystemInfoProvider}. + */ + OperatingSystemInfoProvider getOperatingSystemInfoProvider(); + + /** + * Returns the {@link RuntimeInfoProvider}. + * + * @return Returns the {@link RuntimeInfoProvider}. + */ + RuntimeInfoProvider getRuntimeInfoProvider(); + + /** + * Returns the {@link ThreadInfoProvider}. + * + * @return Returns the {@link ThreadInfoProvider}. + */ + ThreadInfoProvider getThreadInfoProvider(); + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/RuntimeInfoProvider.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/RuntimeInfoProvider.java new file mode 100644 index 000000000..6e9b7b811 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/RuntimeInfoProvider.java @@ -0,0 +1,144 @@ +package info.novatec.inspectit.agent.sensor.platform.provider; + +/** + * The management interface for the runtime system of the Java virtual machine. + * + * @author Eduard Tudenhoefner + * + */ +public interface RuntimeInfoProvider { + + /** + * Returns the name of the Just-in-time (JIT) compiler. + * + * @return the name of the JIT compiler. + */ + String getJitCompilerName(); + + /** + * Returns the approximate accumlated elapsed time (in milliseconds) spent in compilation. If + * multiple threads are used for compilation, this value is summation of the approximate time + * that each thread spent in compilation. + * + *

+ * This method is optionally supported by the platform. A Java virtual machine implementation + * may not support the compilation time monitoring. The + * {@link #isCompilationTimeMonitoringSupported} method can be used to determine if the Java + * virtual machine supports this operation. + * + *

+ * This value does not indicate the level of performance of the Java virtual machine and is not + * intended for performance comparisons of different virtual machine implementations. The + * implementations may have different definitions and different measurements of the compilation + * time. + * + * @return Compilation time in milliseconds + */ + long getTotalCompilationTime(); + + /** + * Returns the total number of classes that have been loaded since the Java virtual machine has + * started execution. + * + * @return the total number of classes loaded. + * + */ + long getTotalLoadedClassCount(); + + /** + * Returns the number of classes that are currently loaded in the Java virtual machine. + * + * @return the number of currently loaded classes. + */ + int getLoadedClassCount(); + + /** + * Returns the total number of classes unloaded since the Java virtual machine has started + * execution. + * + * @return the total number of unloaded classes. + */ + long getUnloadedClassCount(); + + /** + * Returns the Java virtual machine implementation name. This method is equivalent to + * {@link System#getProperty System.getProperty("java.vm.name")}. + * + * @return the Java virtual machine implementation name. + */ + String getVmName(); + + /** + * Returns the Java virtual machine implementation vendor. This method is equivalent to + * {@link System#getProperty System.getProperty("java.vm.vendor")}. + * + * @return the Java virtual machine implementation vendor. + */ + String getVmVendor(); + + /** + * Returns the Java virtual machine implementation version. This method is equivalent to + * {@link System#getProperty System.getProperty("java.vm.version")}. + * + * @return the Java virtual machine implementation version. + */ + String getVmVersion(); + + /** + * Returns the Java virtual machine specification name. This method is equivalent to + * {@link System#getProperty System.getProperty("java.vm.specification.name")}. + * + * @return the Java virtual machine specification name. + */ + String getSpecName(); + + /** + * Returns the Java class path that is used by the system class loader to search for class + * files. This method is equivalent to {@link System#getProperty + * System.getProperty("java.class.path")}. + * + *

+ * Multiple paths in the Java class path are separated by the path separator character of the + * platform of the Java virtual machine being monitored. + * + * @return the Java class path. + */ + String getClassPath(); + + /** + * Returns the Java library path. This method is equivalent to {@link System#getProperty + * System.getProperty("java.library.path")}. + * + *

+ * Multiple paths in the Java library path are separated by the path separator character of the + * platform of the Java virtual machine being monitored. + * + * @return the Java library path. + */ + String getLibraryPath(); + + /** + * Returns the boot class path that is used by the bootstrap class loader to search for class + * files. + * + *

+ * Multiple paths in the boot class path are separated by the path separator character of the + * platform on which the Java virtual machine is running. + * + *

+ * A Java virtual machine implementation may not support the boot class path mechanism for the + * bootstrap class loader to search for class files. The {@link #isBootClassPathSupported} + * method can be used to determine if the Java virtual machine supports this method. + * + * @return the boot class path. + */ + String getBootClassPath(); + + /** + * Returns the uptime of the Java virtual machine in milliseconds. + * + * @return uptime of the Java virtual machine in milliseconds. + */ + long getUptime(); + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/ThreadInfoProvider.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/ThreadInfoProvider.java new file mode 100644 index 000000000..45a25b1f1 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/ThreadInfoProvider.java @@ -0,0 +1,40 @@ +package info.novatec.inspectit.agent.sensor.platform.provider; + +/** + * The management interface for the thread system of the Java virtual machine. + * + * @author Eduard Tudenhoefner + * + */ +public interface ThreadInfoProvider { + + /** + * Returns the current number of live threads including both daemon and non-daemon threads. + * + * @return the current number of live threads. + */ + int getThreadCount(); + + /** + * Returns the peak live thread count since the Java virtual machine started or peak was reset. + * + * @return the peak live thread count. + */ + int getPeakThreadCount(); + + /** + * Returns the total number of threads created and also started since the Java virtual machine + * started. + * + * @return the total number of threads started. + */ + long getTotalStartedThreadCount(); + + /** + * Returns the current number of live daemon threads. + * + * @return the current number of live daemon threads. + */ + int getDaemonThreadCount(); + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultMemoryInfoProvider.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultMemoryInfoProvider.java new file mode 100644 index 000000000..022da0a74 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultMemoryInfoProvider.java @@ -0,0 +1,37 @@ +package info.novatec.inspectit.agent.sensor.platform.provider.def; + +import info.novatec.inspectit.agent.sensor.platform.provider.MemoryInfoProvider; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryUsage; + +/** + * Uses the {@link java.lang.management.MemoryMXBean} in order to retrieve all information that are + * provided here. + * + * @author Eduard Tudenhoefner + * + */ +public class DefaultMemoryInfoProvider implements MemoryInfoProvider { + + /** + * The MXBean used to retrieve heap memory information. + */ + private MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); + + /** + * {@inheritDoc} + */ + public MemoryUsage getHeapMemoryUsage() { + return memoryBean.getHeapMemoryUsage(); + } + + /** + * {@inheritDoc} + */ + public MemoryUsage getNonHeapMemoryUsage() { + return memoryBean.getNonHeapMemoryUsage(); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultOperatingSystemInfoProvider.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultOperatingSystemInfoProvider.java new file mode 100644 index 000000000..becd03e48 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultOperatingSystemInfoProvider.java @@ -0,0 +1,111 @@ +package info.novatec.inspectit.agent.sensor.platform.provider.def; + +import info.novatec.inspectit.agent.sensor.platform.provider.OperatingSystemInfoProvider; + +import java.lang.management.ManagementFactory; + +/** + * Uses the {@link java.lang.management.OperatingSystemMXBean} in order to retrieve a subpart of the + * needed information. Only the methods {@link #getArch()}, {@link #getAvailableProcessors()}, + * {@link #getName()}, {@link #getVersion()}, and {@link #getSystemLoadAverage()} provide data. All + * other methods in this class return -1L values. + * + * @author Eduard Tudenhoefner + * + */ +public class DefaultOperatingSystemInfoProvider implements OperatingSystemInfoProvider { + + /** + * The operating system bean used to retrieve information about the underlying operating system. + */ + private java.lang.management.OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); + + /** + * {@inheritDoc} + */ + public String getName() { + try { + return osBean.getName(); + } catch (SecurityException e) { + return ""; + } + } + + /** + * {@inheritDoc} + */ + public String getArch() { + try { + return osBean.getArch(); + } catch (SecurityException e) { + return ""; + } + } + + /** + * {@inheritDoc} + */ + public String getVersion() { + try { + return osBean.getVersion(); + } catch (SecurityException e) { + return ""; + } + } + + /** + * {@inheritDoc} + */ + public int getAvailableProcessors() { + return osBean.getAvailableProcessors(); + } + + /** + * {@inheritDoc} + */ + public long getCommittedVirtualMemorySize() { + return -1L; + } + + /** + * {@inheritDoc} + */ + public long getFreePhysicalMemorySize() { + return -1L; + } + + /** + * {@inheritDoc} + */ + public long getFreeSwapSpaceSize() { + return -1L; + } + + /** + * {@inheritDoc} + */ + public long getProcessCpuTime() { + return -1L; + } + + /** + * {@inheritDoc} + */ + public long getTotalPhysicalMemorySize() { + return -1L; + } + + /** + * {@inheritDoc} + */ + public long getTotalSwapSpaceSize() { + return -1L; + } + + /** + * {@inheritDoc} + */ + public float retrieveCpuUsage() { + return -1.0f; + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultPlatformSensorInfoProvider.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultPlatformSensorInfoProvider.java new file mode 100644 index 000000000..a805ae2dd --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultPlatformSensorInfoProvider.java @@ -0,0 +1,65 @@ +package info.novatec.inspectit.agent.sensor.platform.provider.def; + +import info.novatec.inspectit.agent.sensor.platform.provider.MemoryInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.OperatingSystemInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.PlatformSensorInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.RuntimeInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.ThreadInfoProvider; + +/** + * Default {@link PlatformSensorInfoProvider}. + * + * @author Ivan Senic + * + */ +public class DefaultPlatformSensorInfoProvider implements PlatformSensorInfoProvider { + + /** + * {@link MemoryInfoProvider}. + */ + private static final MemoryInfoProvider MEMORY_INFO_PROVIDER = new DefaultMemoryInfoProvider(); + + /** + * {@link OperatingSystemInfoProvider}. + */ + private static final OperatingSystemInfoProvider OPERATING_SYSTEM_INFO_PROVIDER = new DefaultOperatingSystemInfoProvider(); + + /** + * {@link RuntimeInfoProvider}. + */ + private static final RuntimeInfoProvider RUNTIME_INFO_PROVIDER = new DefaultRuntimeInfoProvider(); + + /** + * {@link ThreadInfoProvider}. + */ + private static final ThreadInfoProvider THREAD_INFO_PROVIDER = new DefaultThreadInfoProvider(); + + /** + * {@inheritDoc} + */ + public MemoryInfoProvider getMemoryInfoProvider() { + return MEMORY_INFO_PROVIDER; + } + + /** + * {@inheritDoc} + */ + public OperatingSystemInfoProvider getOperatingSystemInfoProvider() { + return OPERATING_SYSTEM_INFO_PROVIDER; + } + + /** + * {@inheritDoc} + */ + public RuntimeInfoProvider getRuntimeInfoProvider() { + return RUNTIME_INFO_PROVIDER; + } + + /** + * {@inheritDoc} + */ + public ThreadInfoProvider getThreadInfoProvider() { + return THREAD_INFO_PROVIDER; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultRuntimeInfoProvider.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultRuntimeInfoProvider.java new file mode 100644 index 000000000..bcbcfab95 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultRuntimeInfoProvider.java @@ -0,0 +1,161 @@ +package info.novatec.inspectit.agent.sensor.platform.provider.def; + +import info.novatec.inspectit.agent.sensor.platform.provider.RuntimeInfoProvider; + +import java.lang.management.ClassLoadingMXBean; +import java.lang.management.CompilationMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; + +/** + * Uses the {@link java.lang.management.CompilationMXBean}, + * {@link java.lang.management.ClassLoadingMXBean}, and {@link java.lang.management.RuntimeMXBean} + * in order to retrieve all of the provided information. + * + * @author Eduard Tudenhoefner + * + */ +public class DefaultRuntimeInfoProvider implements RuntimeInfoProvider { + + /** + * The MXBean used to retrieve information from the compilation system. + */ + private CompilationMXBean compilationBean = ManagementFactory.getCompilationMXBean(); + + /** + * The MXBean used to retrieve information from the class loading system. + */ + private ClassLoadingMXBean classLoadingBean = ManagementFactory.getClassLoadingMXBean(); + + /** + * The MXBean used to retrieve information from the runtime system of the underlying Virtual + * Machine. + */ + private RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean(); + + /** + * {@inheritDoc} + */ + public long getTotalCompilationTime() { + try { + return compilationBean.getTotalCompilationTime(); + } catch (UnsupportedOperationException e) { + return -1L; + } + } + + /** + * {@inheritDoc} + */ + public long getTotalLoadedClassCount() { + return classLoadingBean.getTotalLoadedClassCount(); + } + + /** + * {@inheritDoc} + */ + public int getLoadedClassCount() { + return classLoadingBean.getLoadedClassCount(); + } + + /** + * {@inheritDoc} + */ + public long getUnloadedClassCount() { + return classLoadingBean.getUnloadedClassCount(); + } + + /** + * {@inheritDoc} + */ + public String getJitCompilerName() { + return compilationBean.getName(); + } + + /** + * {@inheritDoc} + */ + public String getVmName() { + try { + return runtimeBean.getVmName(); + } catch (SecurityException e) { + return ""; + } + } + + /** + * {@inheritDoc} + */ + public String getVmVendor() { + try { + return runtimeBean.getVmVendor(); + } catch (SecurityException e) { + return ""; + } + } + + /** + * {@inheritDoc} + */ + public String getVmVersion() { + try { + return runtimeBean.getVmVersion(); + } catch (SecurityException e) { + return ""; + } + } + + /** + * {@inheritDoc} + */ + public String getSpecName() { + try { + return runtimeBean.getSpecName(); + } catch (SecurityException e) { + return ""; + } + } + + /** + * {@inheritDoc} + */ + public String getClassPath() { + try { + return runtimeBean.getClassPath(); + } catch (SecurityException e) { + return ""; + } + } + + /** + * {@inheritDoc} + */ + public String getLibraryPath() { + try { + return runtimeBean.getLibraryPath(); + } catch (SecurityException e) { + return ""; + } + } + + /** + * {@inheritDoc} + */ + public String getBootClassPath() { + try { + return runtimeBean.getBootClassPath(); + } catch (UnsupportedOperationException e) { + return ""; + } catch (SecurityException e) { + return ""; + } + } + + /** + * {@inheritDoc} + */ + public long getUptime() { + return runtimeBean.getUptime(); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultThreadInfoProvider.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultThreadInfoProvider.java new file mode 100644 index 000000000..5671762a4 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/def/DefaultThreadInfoProvider.java @@ -0,0 +1,48 @@ +package info.novatec.inspectit.agent.sensor.platform.provider.def; + +import info.novatec.inspectit.agent.sensor.platform.provider.ThreadInfoProvider; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; + +/** + * Uses the {@link java.lang.management.ThreadMXBean} in order to retrieve all needed information. + * + * @author Eduard Tudenhoefner + * + */ +public class DefaultThreadInfoProvider implements ThreadInfoProvider { + /** + * The MXBean used to retrieve information from the thread system. + */ + private ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); + + /** + * {@inheritDoc} + */ + public int getThreadCount() { + return threadBean.getThreadCount(); + } + + /** + * {@inheritDoc} + */ + public int getPeakThreadCount() { + return threadBean.getPeakThreadCount(); + } + + /** + * {@inheritDoc} + */ + public long getTotalStartedThreadCount() { + return threadBean.getTotalStartedThreadCount(); + } + + /** + * {@inheritDoc} + */ + public int getDaemonThreadCount() { + return threadBean.getDaemonThreadCount(); + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/factory/PlatformSensorInfoProviderFactory.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/factory/PlatformSensorInfoProviderFactory.java new file mode 100644 index 000000000..111b6850b --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/factory/PlatformSensorInfoProviderFactory.java @@ -0,0 +1,103 @@ +package info.novatec.inspectit.agent.sensor.platform.provider.factory; + +import info.novatec.inspectit.agent.sensor.platform.provider.PlatformSensorInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.def.DefaultPlatformSensorInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.ibm.IbmJava6PlatformSensorInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.sun.SunPlatformSensorInfoProvider; +import info.novatec.inspectit.util.UnderlyingSystemInfo; +import info.novatec.inspectit.util.UnderlyingSystemInfo.JavaVersion; +import info.novatec.inspectit.util.UnderlyingSystemInfo.JvmProvider; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class decides which {@link PlatformSensorInfoProvider} will be used. + * + * @author Ivan Senic + * + */ +public final class PlatformSensorInfoProviderFactory { + + /** + * The logger of this class. Initialized manually. + */ + private static final Logger LOG = LoggerFactory.getLogger(PlatformSensorInfoProviderFactory.class); + + /** + * {@link PlatformSensorInfoProvider} when the agent is running on the Sun JVM. + */ + private static volatile PlatformSensorInfoProvider platformSensorInfoProvider; + + /** + * Because the factory should always return same implementation of the + * {@link PlatformSensorInfoProvider} for one virtual machine, we can initialize the correct one + * in the static class initialization, and then simply always return the one we have created. + */ + static { + if (UnderlyingSystemInfo.JVM_PROVIDER == JvmProvider.SUN || UnderlyingSystemInfo.JVM_PROVIDER == JvmProvider.ORACLE) { + if (null == platformSensorInfoProvider) { + createSunPlatformSensorInfoProvider(); + } + } else if (UnderlyingSystemInfo.JVM_PROVIDER == JvmProvider.IBM && (UnderlyingSystemInfo.JAVA_VERSION == JavaVersion.JAVA_1_6 || UnderlyingSystemInfo.JAVA_VERSION == JavaVersion.JAVA_1_7)) { + if (null == platformSensorInfoProvider) { + try { + createIbmJava6PlatformSensorInfoProvider(); + } catch (Exception e) { + LOG.warn("Creation of the Platform Sensor Info Provider for IBM virtual machine failed.", e); + } + } + } + + // if nothing is returned, the go for the default + if (null == platformSensorInfoProvider) { + createDefaultPlatformSensorInfoProvider(); + } + } + + /** + * Private constructor. + */ + private PlatformSensorInfoProviderFactory() { + } + + /** + * Returns the correct {@link PlatformSensorInfoProvider} based on the JVM vendor. + * + * @return {@link PlatformSensorInfoProvider} + * @see UnderlyingSystemInfo + */ + public static PlatformSensorInfoProvider getPlatformSensorInfoProvider() { + return platformSensorInfoProvider; + } + + /** + * Creates the {@link IbmJava6PlatformSensorInfoProvider}. + * + * @throws Exception + * If the IBM platform sensor creation throws an exception. + */ + private static synchronized void createIbmJava6PlatformSensorInfoProvider() throws Exception { + if (null == platformSensorInfoProvider) { + platformSensorInfoProvider = new IbmJava6PlatformSensorInfoProvider(); + } + } + + /** + * Creates the {@link SunPlatformSensorInfoProvider}. + */ + private static synchronized void createSunPlatformSensorInfoProvider() { + if (null == platformSensorInfoProvider) { + platformSensorInfoProvider = new SunPlatformSensorInfoProvider(); + } + } + + /** + * Creates the {@link DefaultPlatformSensorInfoProvider}. + */ + private static synchronized void createDefaultPlatformSensorInfoProvider() { + if (null == platformSensorInfoProvider) { + platformSensorInfoProvider = new DefaultPlatformSensorInfoProvider(); + } + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/ibm/IbmJava6OperatingSystemInfoProvider.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/ibm/IbmJava6OperatingSystemInfoProvider.java new file mode 100644 index 000000000..7981f27a7 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/ibm/IbmJava6OperatingSystemInfoProvider.java @@ -0,0 +1,223 @@ +package info.novatec.inspectit.agent.sensor.platform.provider.ibm; + +import info.novatec.inspectit.agent.sensor.platform.provider.def.DefaultOperatingSystemInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.util.CpuUsageCalculator; + +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.lang.reflect.Method; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class provide the information about the operating system when the + * {@value #IBM_OPERATING_SYSTEM_MX_BEAN_CLASS} is available. The information provided by this class + * is gathered using the reflection. + *

+ * Note that the methods {@link #getFreePhysicalMemorySize()} and {@link #getTotalSwapSpaceSize()} + * will not be able to provide different values than the default ones, because the IBM does not + * provide the information about the OS swap size. + * + * @author Ivan Senic + * + */ +public class IbmJava6OperatingSystemInfoProvider extends DefaultOperatingSystemInfoProvider { + + /** + * The logger of this class. Initialized manually. + */ + private static final Logger LOG = LoggerFactory.getLogger(IbmJava6OperatingSystemInfoProvider.class); + + /** + * FQN name of the IBMs class that provides operating system management. + */ + private static final String IBM_OPERATING_SYSTEM_MX_BEAN_CLASS = "com.ibm.lang.management.OperatingSystemMXBeanImpl"; + + /** + * The managed bean to retrieve information about the uptime of the JVM. + */ + private RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean(); + + /** + * The calculator used to calculate and retrieve the current CPU usage of the underlying JVM. + */ + private CpuUsageCalculator cpuCalculator = new CpuUsageCalculator(); + + /** + * Method for getting the committed virtual memory size. + */ + private Method committedVirtualMemorySizeMethod; + + /** + * Method for getting the free physical memory size. + */ + private Method freePhysicalMemorySizeMethod; + + /** + * Method for getting the process CPU time. + */ + private Method processCpuTimeMethod; + + /** + * Method for getting the total physical memory size. + */ + private Method totalPhysicalMemorySizeMethod; + + /** + * Instance to be used with reflection. + */ + private Object ibmOperatingSystemMxBeanInstance; + + /** + * Default constructor. + * + * @throws Exception + * If the initialization fails. + */ + public IbmJava6OperatingSystemInfoProvider() throws Exception { + initAndCheckEnvironment(); + } + + /** + * Initializes the class and checks if every method needed to be accessed with reflection is + * available and is returning the correct value. + * + * @throws Exception + * If any exception occurs during the initialization. + */ + private void initAndCheckEnvironment() throws Exception { + Class ibmOperatingSystemMxBeanClass = null; + ibmOperatingSystemMxBeanClass = Class.forName(IBM_OPERATING_SYSTEM_MX_BEAN_CLASS); + + if (null != ibmOperatingSystemMxBeanClass) { + Method getInstanceMethod = ibmOperatingSystemMxBeanClass.getDeclaredMethod("getInstance"); + getInstanceMethod.setAccessible(true); + if (null != getInstanceMethod) { + ibmOperatingSystemMxBeanInstance = getInstanceMethod.invoke(null); + } + + if (null != ibmOperatingSystemMxBeanInstance) { + // getProcessVirtualMemorySize method + committedVirtualMemorySizeMethod = getMethod(ibmOperatingSystemMxBeanClass, "getProcessVirtualMemorySize"); + Object result = getValueFromMethodInvocation(ibmOperatingSystemMxBeanInstance, committedVirtualMemorySizeMethod); + if (!(result instanceof Number)) { + throw new Exception("Result of getProcessVirtualMemorySize() method invocation is not a number. Result was: " + result); + } + + // getFreePhysicalMemorySize method + freePhysicalMemorySizeMethod = getMethod(ibmOperatingSystemMxBeanClass, "getFreePhysicalMemorySize"); + result = getValueFromMethodInvocation(ibmOperatingSystemMxBeanInstance, freePhysicalMemorySizeMethod); + if (!(result instanceof Number)) { + throw new Exception("Result of getFreePhysicalMemorySize() method invocation is not a number. Result was: " + result); + } + + // getProcessCpuTime method + processCpuTimeMethod = getMethod(ibmOperatingSystemMxBeanClass, "getProcessCpuTime"); + result = getValueFromMethodInvocation(ibmOperatingSystemMxBeanInstance, processCpuTimeMethod); + if (!(result instanceof Number)) { + throw new Exception("Result of getProcessCpuTime() method invocation is not a number. Result was: " + result); + } + + // getTotalPhysicalMemory method + totalPhysicalMemorySizeMethod = getMethod(ibmOperatingSystemMxBeanClass, "getTotalPhysicalMemory"); + result = getValueFromMethodInvocation(ibmOperatingSystemMxBeanInstance, totalPhysicalMemorySizeMethod); + if (!(result instanceof Number)) { + throw new Exception("Result of getTotalPhysicalMemory() method invocation is not a number. Result was: " + result); + } + + } + } + } + + /** + * {@inheritDoc} + */ + public long getCommittedVirtualMemorySize() { + Number result = getValueFromMethodInvocation(ibmOperatingSystemMxBeanInstance, committedVirtualMemorySizeMethod); + return result.longValue(); + } + + /** + * {@inheritDoc} + */ + public long getFreePhysicalMemorySize() { + Number result = getValueFromMethodInvocation(ibmOperatingSystemMxBeanInstance, freePhysicalMemorySizeMethod); + return result.longValue(); + } + + /** + * {@inheritDoc} + */ + public long getProcessCpuTime() { + Number result = getValueFromMethodInvocation(ibmOperatingSystemMxBeanInstance, processCpuTimeMethod); + // by IBM Documentation the process cpu time is in 100 ns units + // since we need ns, we need to multiply the result by 100 + return result.longValue() * 100; + + } + + /** + * {@inheritDoc} + */ + public long getTotalPhysicalMemorySize() { + Number result = getValueFromMethodInvocation(ibmOperatingSystemMxBeanInstance, totalPhysicalMemorySizeMethod); + return result.longValue(); + + } + + /** + * {@inheritDoc} + */ + public float retrieveCpuUsage() { + cpuCalculator.setUptime(runtimeBean.getUptime()); + cpuCalculator.setProcessCpuTime(this.getProcessCpuTime()); + cpuCalculator.setAvailableProcessors(this.getAvailableProcessors()); + cpuCalculator.updateCpuUsage(); + + return cpuCalculator.getCpuUsage(); + } + + /** + * Loads the wanted method from the class and makes it accessible. + * + * @param clazz + * Class + * @param methodName + * Method name + * @param parameterTypes + * Parameters. + * @return Returns method object. + * @throws Exception + * If any {@link Exception} occurs during getting the method. + */ + private Method getMethod(Class clazz, String methodName, Class... parameterTypes) throws Exception { + Method m = clazz.getDeclaredMethod(methodName, parameterTypes); + m.setAccessible(true); + return m; + + } + + /** + * Invokes the given method on the given instance. + * + * @param instance + * instance to perform invocation on + * @param method + * Method to invoke + * @param args + * Arguments + * @param + * The result type. + * @return Returns method invocation result, or null if method invocation fails for any reason. + */ + @SuppressWarnings("unchecked") + private V getValueFromMethodInvocation(Object instance, Method method, Object... args) { + try { + return (V) method.invoke(instance, args); + } catch (Exception e) { + LOG.warn("Exception throw during method invocation.", e); + return null; + } + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/ibm/IbmJava6PlatformSensorInfoProvider.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/ibm/IbmJava6PlatformSensorInfoProvider.java new file mode 100644 index 000000000..fee7179bc --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/ibm/IbmJava6PlatformSensorInfoProvider.java @@ -0,0 +1,92 @@ +package info.novatec.inspectit.agent.sensor.platform.provider.ibm; + +import info.novatec.inspectit.agent.sensor.platform.provider.MemoryInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.OperatingSystemInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.PlatformSensorInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.RuntimeInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.ThreadInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.def.DefaultMemoryInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.def.DefaultRuntimeInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.def.DefaultThreadInfoProvider; + +/** + * {@link PlatformSensorInfoProvider} for IBM Java virtual machine. Only for Java version 1.6+. + * + * @author Ivan Senic + * + */ +public class IbmJava6PlatformSensorInfoProvider implements PlatformSensorInfoProvider { + + /** + * {@link MemoryInfoProvider}. + */ + private static final MemoryInfoProvider MEMORY_INFO_PROVIDER = new DefaultMemoryInfoProvider(); + + /** + * {@link OperatingSystemInfoProvider}. + */ + private static OperatingSystemInfoProvider operatingSystemInfoProvider; + + /** + * {@link RuntimeInfoProvider}. + */ + private static final RuntimeInfoProvider RUNTIME_INFO_PROVIDER = new DefaultRuntimeInfoProvider(); + + /** + * {@link ThreadInfoProvider}. + */ + private static final ThreadInfoProvider THREAD_INFO_PROVIDER = new DefaultThreadInfoProvider(); + + /** + * Default constructor. + * + * @throws Exception + * If the SystemInformationProvider could not be created. + */ + public IbmJava6PlatformSensorInfoProvider() throws Exception { + if (null == operatingSystemInfoProvider) { + createOperatingSystemInfoProvider(); + } + } + + /** + * Creates the OperatingSystemInfoProvider for IBM Java6. + * + * @throws Exception + * on error. + */ + private static synchronized void createOperatingSystemInfoProvider() throws Exception { + if (null == operatingSystemInfoProvider) { + operatingSystemInfoProvider = new IbmJava6OperatingSystemInfoProvider(); + } + } + + /** + * {@inheritDoc} + */ + public MemoryInfoProvider getMemoryInfoProvider() { + return MEMORY_INFO_PROVIDER; + } + + /** + * {@inheritDoc} + */ + public OperatingSystemInfoProvider getOperatingSystemInfoProvider() { + return operatingSystemInfoProvider; + } + + /** + * {@inheritDoc} + */ + public RuntimeInfoProvider getRuntimeInfoProvider() { + return RUNTIME_INFO_PROVIDER; + } + + /** + * {@inheritDoc} + */ + public ThreadInfoProvider getThreadInfoProvider() { + return THREAD_INFO_PROVIDER; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/sun/SunOperatingSystemInfoProvider.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/sun/SunOperatingSystemInfoProvider.java new file mode 100644 index 000000000..e95803ebf --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/sun/SunOperatingSystemInfoProvider.java @@ -0,0 +1,118 @@ +package info.novatec.inspectit.agent.sensor.platform.provider.sun; + +import info.novatec.inspectit.agent.sensor.platform.provider.OperatingSystemInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.util.CpuUsageCalculator; + +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; + +import com.sun.management.OperatingSystemMXBean; + +/** + * This class retrieves all the data as {@link OperatingSystemInfoProvider} from + * {@link OperatingSystemMXBean} from Sun. + * + * @see com.sun.management.OperatingSystemMXBean + * + * @author Eduard Tudenhoefner + * + */ +public class SunOperatingSystemInfoProvider implements OperatingSystemInfoProvider { + + /** + * The managed bean to retrieve the OS information from. + */ + private OperatingSystemMXBean osBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); + + /** + * The managed bean to retrieve information about the uptime of the JVM. + */ + private RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean(); + + /** + * The calculator used to calculate and retrieve the current CPU usage of the underlying JVM. + */ + private CpuUsageCalculator cpuCalculator = new CpuUsageCalculator(); + + /** + * {@inheritDoc} + */ + public String getName() { + return osBean.getName(); + } + + /** + * {@inheritDoc} + */ + public String getArch() { + return osBean.getArch(); + } + + /** + * {@inheritDoc} + */ + public String getVersion() { + return osBean.getVersion(); + } + + /** + * {@inheritDoc} + */ + public int getAvailableProcessors() { + return osBean.getAvailableProcessors(); + } + + /** + * {@inheritDoc} + */ + public long getCommittedVirtualMemorySize() { + return osBean.getCommittedVirtualMemorySize(); + } + + /** + * {@inheritDoc} + */ + public long getFreePhysicalMemorySize() { + return osBean.getFreePhysicalMemorySize(); + } + + /** + * {@inheritDoc} + */ + public long getFreeSwapSpaceSize() { + return osBean.getFreeSwapSpaceSize(); + } + + /** + * {@inheritDoc} + */ + public long getProcessCpuTime() { + return osBean.getProcessCpuTime(); + } + + /** + * {@inheritDoc} + */ + public long getTotalPhysicalMemorySize() { + return osBean.getTotalPhysicalMemorySize(); + } + + /** + * {@inheritDoc} + */ + public long getTotalSwapSpaceSize() { + return osBean.getTotalSwapSpaceSize(); + } + + /** + * {@inheritDoc} + */ + public float retrieveCpuUsage() { + cpuCalculator.setUptime(runtimeBean.getUptime()); + cpuCalculator.setProcessCpuTime(this.getProcessCpuTime()); + cpuCalculator.setAvailableProcessors(this.getAvailableProcessors()); + cpuCalculator.updateCpuUsage(); + + return cpuCalculator.getCpuUsage(); + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/sun/SunPlatformSensorInfoProvider.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/sun/SunPlatformSensorInfoProvider.java new file mode 100644 index 000000000..3bec3a2a7 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/sun/SunPlatformSensorInfoProvider.java @@ -0,0 +1,68 @@ +package info.novatec.inspectit.agent.sensor.platform.provider.sun; + +import info.novatec.inspectit.agent.sensor.platform.provider.MemoryInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.OperatingSystemInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.PlatformSensorInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.RuntimeInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.ThreadInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.def.DefaultMemoryInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.def.DefaultRuntimeInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.def.DefaultThreadInfoProvider; + +/** + * Special {@link PlatformSensorInfoProvider} for SunVM.. + * + * @author Ivan Senic + * + */ +public class SunPlatformSensorInfoProvider implements PlatformSensorInfoProvider { + + /** + * {@link MemoryInfoProvider}. + */ + private static final MemoryInfoProvider MEMORY_INFO_PROVIDER = new DefaultMemoryInfoProvider(); + + /** + * {@link OperatingSystemInfoProvider}. + */ + private static final OperatingSystemInfoProvider OPERATING_SYSTEM_INFO_PROVIDER = new SunOperatingSystemInfoProvider(); + + /** + * {@link RuntimeInfoProvider}. + */ + private static final RuntimeInfoProvider RUNTIME_INFO_PROVIDER = new DefaultRuntimeInfoProvider(); + + /** + * {@link ThreadInfoProvider}. + */ + private static final ThreadInfoProvider THREAD_INFO_PROVIDER = new DefaultThreadInfoProvider(); + + /** + * {@inheritDoc} + */ + public MemoryInfoProvider getMemoryInfoProvider() { + return MEMORY_INFO_PROVIDER; + } + + /** + * {@inheritDoc} + */ + public OperatingSystemInfoProvider getOperatingSystemInfoProvider() { + return OPERATING_SYSTEM_INFO_PROVIDER; + } + + /** + * {@inheritDoc} + */ + public RuntimeInfoProvider getRuntimeInfoProvider() { + return RUNTIME_INFO_PROVIDER; + } + + /** + * {@inheritDoc} + */ + public ThreadInfoProvider getThreadInfoProvider() { + return THREAD_INFO_PROVIDER; + } + +} diff --git a/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/util/CpuUsageCalculator.java b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/util/CpuUsageCalculator.java new file mode 100644 index 000000000..585bc7c9f --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/sensor/platform/provider/util/CpuUsageCalculator.java @@ -0,0 +1,98 @@ +package info.novatec.inspectit.agent.sensor.platform.provider.util; + +/** + * This class is used to calculate the cpu usage of the underlying Virtual Machine. + * + * @author Eduard Tudenhoefner + * + */ +public class CpuUsageCalculator { + + /** + * The uptime. + */ + private long uptime = -1L; + + /** + * The process cpu time. + */ + private long processCpuTime = -1L; + + /** + * The available processors. + */ + private int availableProcessors = 0; + + /** + * The previous uptime of the Virtual Machine. + */ + private long prevUptime = 0L; + + /** + * The previous processCpuTime. + */ + private long prevProcessCpuTime = 0L; + + /** + * The cpu usage. + */ + private float cpuUsage = 0.0F; + + /** + * Gets {@link #cpuUsage}. + * + * @return {@link #cpuUsage} + */ + public float getCpuUsage() { + return cpuUsage; + } + + /** + * Sets {@link #uptime}. + * + * @param uptime + * New value for {@link #uptime} + */ + public void setUptime(long uptime) { + this.uptime = uptime; + } + + /** + * Sets {@link #processCpuTime}. + * + * @param processCpuTime + * New value for {@link #processCpuTime} + */ + public void setProcessCpuTime(long processCpuTime) { + this.processCpuTime = processCpuTime; + } + + /** + * Sets {@link #availableProcessors}. + * + * @param availableProcessors + * New value for {@link #availableProcessors} + */ + public void setAvailableProcessors(int availableProcessors) { + this.availableProcessors = availableProcessors; + } + + /** + * Calculates the current cpuUsage in percent. + * + * elapsedCpu is in ns and elapsedTime is in ms. cpuUsage could go higher than 100% because + * elapsedTime and elapsedCpu are not fetched simultaneously. Limit to 99% to avoid showing a + * scale from 0% to 200%. + * + */ + public void updateCpuUsage() { + if (prevUptime > 0L && this.uptime > prevUptime) { + long elapsedCpu = this.processCpuTime - prevProcessCpuTime; + long elapsedTime = this.uptime - prevUptime; + + cpuUsage = Math.min(99F, elapsedCpu / (elapsedTime * 10000F * this.availableProcessors)); + } + this.prevUptime = this.uptime; + this.prevProcessCpuTime = this.processCpuTime; + } +} \ No newline at end of file diff --git a/Agent/src/info/novatec/inspectit/agent/spring/PrototypesProvider.java b/Agent/src/info/novatec/inspectit/agent/spring/PrototypesProvider.java new file mode 100644 index 000000000..c9e540fb3 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/spring/PrototypesProvider.java @@ -0,0 +1,64 @@ +package info.novatec.inspectit.agent.spring; + +import info.novatec.inspectit.storage.nio.stream.ExtendedByteBufferOutputStream; +import info.novatec.inspectit.storage.nio.stream.SocketExtendedByteBufferInputStream; +import info.novatec.inspectit.storage.nio.stream.StreamProvider; +import info.novatec.inspectit.storage.serializer.ISerializerProvider; +import info.novatec.inspectit.storage.serializer.impl.SerializationManager; + +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Provider for all needed prototypes since we don't have spring config files anymore. + * + * @author Ivan Senic + * + */ +@Component +public class PrototypesProvider extends StreamProvider implements ISerializerProvider { + + /** + * Factory for {@link SerializationManager}. + */ + @Autowired + private ObjectFactory serializationManagerFactory; + + /** + * Factory for {@link ExtendedByteBufferOutputStream}. + */ + @Autowired + private ObjectFactory extendedByteBufferOutputStreamFactory; + + /** + * Factory for {@link SocketExtendedByteBufferInputStream}. + */ + @Autowired + private ObjectFactory socketExtendedByteBufferInputStreamFactory; + + /** + * Returns the new {@link SerializationManager} enhanced by Spring. + * + * @return Returns the new {@link SerializationManager} enhanced by Spring. + */ + public SerializationManager createSerializer() { + return serializationManagerFactory.getObject(); + } + + /** + * {@inheritDoc} + */ + @Override + protected ExtendedByteBufferOutputStream createExtendedByteBufferOutputStream() { + return extendedByteBufferOutputStreamFactory.getObject(); + } + + /** + * {@inheritDoc} + */ + @Override + protected SocketExtendedByteBufferInputStream createSocketExtendedByteBufferInputStream() { + return socketExtendedByteBufferInputStreamFactory.getObject(); + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/spring/SpringConfiguration.java b/Agent/src/info/novatec/inspectit/agent/spring/SpringConfiguration.java new file mode 100644 index 000000000..c67915d47 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/spring/SpringConfiguration.java @@ -0,0 +1,146 @@ +package info.novatec.inspectit.agent.spring; + +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.config.impl.MethodSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.PlatformSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.StrategyConfig; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.core.io.ClassPathResource; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +/** + * Post process configuration storage to define buffer and sending strategy beans. + * + * @author Ivan Senic + * + */ +@Configuration +@ComponentScan("info.novatec.inspectit") +public class SpringConfiguration implements BeanDefinitionRegistryPostProcessor { + + /** + * Registry to add bean definitions to. + */ + private BeanDefinitionRegistry registry; + + /** + * Bean factory to force initialization of manually defined beans. + */ + private ConfigurableListableBeanFactory beanFactory; + + /** + * {@inheritDoc} + */ + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + /** + * {@inheritDoc} + */ + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + this.registry = registry; + } + + /** + * Returns {@link PropertyPlaceholderConfigurer} for the Agent. + * + * @return Returns {@link PropertyPlaceholderConfigurer} for the Agent. + */ + @Bean + public static PropertyPlaceholderConfigurer properties() { + PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); + ClassPathResource[] resources = new ClassPathResource[] { new ClassPathResource("/config/bytebufferpool.properties") }; + ppc.setLocations(resources); + ppc.setIgnoreUnresolvablePlaceholders(true); + return ppc; + } + + /** + * @return Returns socketReadExecutorService + */ + @Bean(name = "socketReadExecutorService") + @Scope(BeanDefinition.SCOPE_SINGLETON) + public ExecutorService getSocketReadExecutorService() { + ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("inspectit-socket-read-executor-service-thread-%d").setDaemon(true).build(); + return Executors.newFixedThreadPool(1, threadFactory); + } + + /** + * Registers components needed by the configuration to the Spring container. + * + * @param configurationStorage + * {@link IConfigurationStorage} with the settings. + * @throws Exception + * If exception occurs during the registration. + */ + public void registerComponents(IConfigurationStorage configurationStorage) throws Exception { + // buffer strategy + String className = configurationStorage.getBufferStrategyConfig().getClazzName(); + String beanName = "bufferStrategy[" + className + "]"; + registerBeanDefinitionAndInitialize(beanName, className); + + // sending strategies + for (StrategyConfig sendingStrategyConfig : configurationStorage.getSendingStrategyConfigs()) { + className = sendingStrategyConfig.getClazzName(); + beanName = "sendingStrategy[" + className + "]"; + registerBeanDefinitionAndInitialize(beanName, className); + } + + // platform sensor types + for (PlatformSensorTypeConfig platformSensorTypeConfig : configurationStorage.getPlatformSensorTypes()) { + className = platformSensorTypeConfig.getClassName(); + beanName = "platformSensorType[" + className + "]"; + registerBeanDefinitionAndInitialize(beanName, className); + } + + // method sensor types + for (MethodSensorTypeConfig methodSensorTypeConfig : configurationStorage.getMethodSensorTypes()) { + className = methodSensorTypeConfig.getClassName(); + beanName = "methodSensorType[" + className + "]"; + registerBeanDefinitionAndInitialize(beanName, className); + } + + } + + /** + * Creates bean definition for the given class name, registers the definition in the registry + * and immediately invokes the initialization of the bean. + *

+ * This is the only way to initialize the bean definitions that no other component has + * dependency to, since we add the definitions in the moment when the lookup has been finished + * and bean creation has started. + * + * @param beanName + * Name of the bean to register. + * @param className + * Class name of the bean. + * @throws ClassNotFoundException + * If class can not be founded. + */ + private void registerBeanDefinitionAndInitialize(String beanName, String className) throws ClassNotFoundException { + Class clazz = Class.forName(className); + GenericBeanDefinition definition = new GenericBeanDefinition(); + definition.setBeanClass(clazz); + definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE); + definition.setAutowireCandidate(true); + registry.registerBeanDefinition(beanName, definition); + beanFactory.getBean(beanName, clazz); + } +} diff --git a/Agent/src/info/novatec/inspectit/agent/spring/StrategyAndSensorConfiguration.java b/Agent/src/info/novatec/inspectit/agent/spring/StrategyAndSensorConfiguration.java new file mode 100644 index 000000000..49bb49383 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/agent/spring/StrategyAndSensorConfiguration.java @@ -0,0 +1,41 @@ +package info.novatec.inspectit.agent.spring; + +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.config.impl.ConfigurationStorage; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.DependsOn; +import org.springframework.stereotype.Component; + +/** + * This class enables that {@link SpringConfiguration} processes the {@link ConfigurationStorage} + * after it has been successfully initialized. + * + * @author Ivan Senic + * + */ +@Component("strategyAndSensorConfiguration") +@DependsOn({ "configurationReader" }) +public class StrategyAndSensorConfiguration implements InitializingBean { + + /** + * {@link IConfigurationStorage} holding the needed configuration details. + */ + @Autowired + private IConfigurationStorage configurationStorage; + + /** + * {@link SpringConfiguration} to process the {@link IConfigurationStorage}. + */ + @Autowired + private SpringConfiguration springConfiguration; + + /** + * {@inheritDoc} + */ + public void afterPropertiesSet() throws Exception { + springConfiguration.registerComponents(configurationStorage); + } + +} diff --git a/Agent/src/info/novatec/inspectit/util/MessageFormatFormatter.java b/Agent/src/info/novatec/inspectit/util/MessageFormatFormatter.java new file mode 100644 index 000000000..f870b628b --- /dev/null +++ b/Agent/src/info/novatec/inspectit/util/MessageFormatFormatter.java @@ -0,0 +1,49 @@ +package info.novatec.inspectit.util; + +import java.text.MessageFormat; +import java.util.Date; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +/** + * The {@link MessageFormatFormatter} class is used to format the log output of the standard + * java.util.logging package. To activate this formatter, edit the + * logging.properties file in the lib folder of the Java installation or pass a new + * Java Argument named -Djava.util.logging.config.file in the start command line. + * + * @author Patrice Bouillet + * + */ +public class MessageFormatFormatter extends Formatter { + + /** + * The message format object which defines the template for the output if no {@link Throwable} + * object is contained in the {@link LogRecord}. + */ + private static final MessageFormat MESSAGE_FORMAT = new MessageFormat("{0,date,HH:mm:ss,sss} {1} {2} - {3} \n"); + + /** + * The message format object which defines the template for the output if a {@link Throwable} + * object is contained in the {@link LogRecord}. + */ + private static final MessageFormat MESSAGE_FORMAT_THROWN = new MessageFormat("{0,date,HH:mm:ss,sss} {1} {2} - {3} \n {4} \n"); + + /** + * {@inheritDoc} + */ + public String format(LogRecord record) { + Object[] arguments = new Object[5]; + arguments[0] = new Date(record.getMillis()); + arguments[1] = record.getLevel(); + String[] nameSplit = record.getSourceClassName().split("\\."); + arguments[2] = nameSplit[nameSplit.length - 1]; + arguments[3] = record.getMessage(); + if (null != record.getThrown()) { + arguments[4] = record.getThrown().toString(); + return MESSAGE_FORMAT_THROWN.format(arguments); + } else { + return MESSAGE_FORMAT.format(arguments); + } + } + +} diff --git a/Agent/src/info/novatec/inspectit/util/ReflectionCache.java b/Agent/src/info/novatec/inspectit/util/ReflectionCache.java new file mode 100644 index 000000000..60e2fac3f --- /dev/null +++ b/Agent/src/info/novatec/inspectit/util/ReflectionCache.java @@ -0,0 +1,91 @@ +package info.novatec.inspectit.util; + +import java.lang.reflect.Method; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +/** + * Provides caching of Reflection calls. + * + * @author Stefan Siegl + */ +public class ReflectionCache { + + /** + * The logger of this class. Initialized manually. + */ + private static final Logger LOG = LoggerFactory.getLogger(ReflectionCache.class); + + /** + * Cache that holds the Method instances for the Class and method Names. + */ + Cache, Cache> cache = CacheBuilder.newBuilder().weakKeys().softValues().build(); + + /** + * Invokes the given method on the given class. This method uses the cache to only lookup the + * method if the same method was not looked up before for the same class. + * + * Known limitation: The method name is used as key and not the whole signature, so you + * cannot cache methods with the same name but different parameters. The reason is that we do + * not need this feature right now and it would complicate and slow down the cache. + * + * Known limitation: Exceptions are not passed along, but will break the invocation. + * + * @param clazz + * The class on which the method should be invoked. + * @param methodName + * the name of the method. + * @param parameterTypes + * the parameters of the method or null if none are needed. + * @param instance + * the instance on which the method call should be invoked. + * @param values + * the parameter values or null if none are needed. + * @param errorValue + * the value that should be returned in case of an error or null if none + * are needed. + * @return the invocation result. + */ + public Object invokeMethod(Class clazz, String methodName, Class[] parameterTypes, Object instance, Object[] values, Object errorValue) { + if (null == clazz || null == methodName) { + return errorValue; + } + Cache classCache = cache.getIfPresent(clazz); + if (null == classCache) { + // create a new cache and add it. There can be race conditions here. There is no entry + // for class C and caller A and caller B + // want to execute method a or b on the same class. It can thus happen that one + // overwrites the other. This is not a big issue + // as the only result will be that the method will not be cached for one and will be + // cached with the next invocation. This + // approach is way cheaper than synchronisation or copy on write. + classCache = CacheBuilder.newBuilder().expireAfterAccess(20 * 60, TimeUnit.SECONDS).weakKeys().build(); + cache.put(clazz, classCache); + } + + // get method + Method method = classCache.getIfPresent(methodName); + if (null == method) { + try { + method = clazz.getMethod(methodName, parameterTypes); + } catch (Exception e) { + LOG.warn("Could not lookup method " + methodName + " on class " + clazz.getName(), e); + return errorValue; + } + classCache.put(methodName, method); + } + + // invoke method + try { + return method.invoke(instance, values); + } catch (Exception e) { + LOG.warn("Could not invoke method " + methodName + " on instance " + instance, e); + return errorValue; + } + } +} diff --git a/Agent/src/info/novatec/inspectit/util/StringConstraint.java b/Agent/src/info/novatec/inspectit/util/StringConstraint.java new file mode 100644 index 000000000..e2b71fc6b --- /dev/null +++ b/Agent/src/info/novatec/inspectit/util/StringConstraint.java @@ -0,0 +1,185 @@ +package info.novatec.inspectit.util; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class provides methods for string manipulation purposes as cropping a string to a specified + * length. + * + * @author Patrick Eschenbach + */ +public class StringConstraint { + + /** + * The logger of this class. Initialized manually. + */ + private static final Logger LOG = LoggerFactory.getLogger(StringConstraint.class); + + /** + * Boolean indicating whether to use three trailing dots or not. + */ + private static final boolean USE_TRAILING_DOTS = true; + + /** + * Global definition of the maximal string length. + */ + private static final int MAX_STRING_LENGTH = Integer.MAX_VALUE; + + /** + * The effective string length which is defined as the smaller one of the sensor's configuration + * or MAX_STRING_LENGTH. + */ + private int effectiveStringLength; + + /** + * The default constructor which needs one parameter for initialization. + * + * @param parameter + * Parameter map to extract the constrain information. + */ + public StringConstraint(Map parameter) { + effectiveStringLength = MAX_STRING_LENGTH; + + String value = (String) parameter.get("stringLength"); + if (value != null) { + try { + int configStringLength = Integer.parseInt(value); + + // only use the given length if smaller than the max length and not smaller than 0 + if (configStringLength < MAX_STRING_LENGTH && configStringLength >= 0) { + effectiveStringLength = configStringLength; + } + } catch (NumberFormatException e) { + if (LOG.isWarnEnabled()) { + LOG.warn("Property 'stringLength' is not defined correctly. Using unlimited string length."); + } + } + } + } + + /** + * Crops the given string based on this instance's configuration. If the string is shorter than + * the specified string length the given string is not altered. + * + * @param string + * The string to crop. + * @return The cropped string. + */ + public String crop(String string) { + if (null == string || string.length() <= effectiveStringLength) { + return string; + } + + if (effectiveStringLength == 0) { + return ""; + } + + String cropped = string.substring(0, effectiveStringLength); + if (USE_TRAILING_DOTS) { + cropped = appendTrailingDots(cropped); + } + return cropped; + } + + /** + * Analyzes the given map and tries to re-use the values in String arrays and the string arrays + * themself to converse memory. A new String array for values is only created if one entry in + * the values needed cropping to ensure that the original map is not changed. + * + * @param original + * the Map that potentially needs cropping + * @return a new Map that contains cropped Strings that potentially re-use the + * String references of the initial Map if the Strings did not change. + */ + public Map crop(final Map original) { + Map result = new HashMap(original.size()); + + for (Map.Entry entry : original.entrySet()) { + String key = entry.getKey(); + if (null == key) { + continue; + } + + String[] value = entry.getValue(); + + String[] convertedValue = null; + if (null == value) { + convertedValue = new String[1]; + convertedValue[0] = ""; + } else { + boolean croppingWasNeeded = false; + convertedValue = value; + + for (int i = 0, l = value.length; i < l; i++) { + String curValue = value[i]; + String croppingResult = crop(curValue); + + // Identity comparison is on purpose + if (curValue != croppingResult & !croppingWasNeeded) { // NOPMD + // The string reference was changed and thus cropped + croppingWasNeeded = true; + + // we need to change to an own array as we cannot reuse the + // existing array as we would change strings of the application + convertedValue = new String[value.length]; + + System.arraycopy(value, 0, convertedValue, 0, i); + + // and add the one we are currently dealing with + convertedValue[i] = croppingResult; + } + + if (croppingWasNeeded) { + // we already have a copied array so we can add to this. + convertedValue[i] = croppingResult; + } + + // if we do not find anything that needed cropping we just re-use + // the old representation. + } + } + + result.put(key, convertedValue); + } + + return result; + } + + /** + * Crops the given string and adds the given final character to the string's end. If the string + * is shorter than the specified string length the given string is not altered. + * + * @param string + * The string to crop. + * @param finalChar + * The character to append at the end. + * @return A cropped string ending with the given final character. + */ + public String cropKeepFinalCharacter(String string, char finalChar) { + String cropped = crop(string); + + if (null == string || string.equals(cropped)) { + return string; + } + + if (cropped.length() == 0) { + return cropped; + } + return cropped + finalChar; + } + + /** + * Appends three dots to the given string to indicate that the string was cropped. + * + * @param string + * The string to append the dots to. + * @return A new string equal to the given one + three dots. + */ + private String appendTrailingDots(String string) { + return string + "..."; + } +} diff --git a/Agent/src/info/novatec/inspectit/util/ThreadLocalStack.java b/Agent/src/info/novatec/inspectit/util/ThreadLocalStack.java new file mode 100644 index 000000000..b614a3741 --- /dev/null +++ b/Agent/src/info/novatec/inspectit/util/ThreadLocalStack.java @@ -0,0 +1,62 @@ +package info.novatec.inspectit.util; + +import java.util.LinkedList; + +/** + * The ThreadLocalStack class extends {@link ThreadLocal} to have a {@link LinkedList} to be used as + * a stack. Extending {@link ThreadLocal} and using the {@link #initialValue()} method helps to keep + * the variable private to the actual {@link Thread}. A {@link LinkedList} is used because it can be + * easily used as a FIFO stack. + * + * @param + * elements of the linked list. + * + * @author Patrice Bouillet + */ +public class ThreadLocalStack extends ThreadLocal> { + + /** + * {@inheritDoc} + */ + public LinkedList initialValue() { // NOPMD + return new LinkedList(); + } + + /** + * Pushes the specified value onto the stack. + * + * @param value + * the value to push onto the stack. + */ + public void push(E value) { + super.get().addLast(value); + } + + /** + * Returns the last pushed value. + * + * @return The last pushed value. + */ + public E pop() { + return super.get().removeLast(); + } + + /** + * Returns the last pushed value without removing it. + * + * @return The last pushed value. + */ + public E getLast() { + return super.get().getLast(); + } + + /** + * Returns the first value pushed onto the stack. + * + * @return The first value pushed onto the stack. + */ + public E getAndRemoveFirst() { + return super.get().removeFirst(); + } + +} diff --git a/Agent/src/info/novatec/inspectit/util/Timer.java b/Agent/src/info/novatec/inspectit/util/Timer.java new file mode 100644 index 000000000..ecf56a64e --- /dev/null +++ b/Agent/src/info/novatec/inspectit/util/Timer.java @@ -0,0 +1,25 @@ +package info.novatec.inspectit.util; + +import org.springframework.stereotype.Component; + +/** + * Class which was used as a wrapper around a timer factory. As the move to Java 5 was done, the + * factory is currently not needed anymore, but this class stays if some new timer implementations + * will be needed in the future (higher precision, performance, ...). + * + * @author Patrice Bouillet + * + */ +@Component +public class Timer { + + /** + * Returns the current time in milliseconds. + * + * @return The time as a double value. + */ + public double getCurrentTime() { + return System.nanoTime() / 1000000.0d; + } + +} diff --git a/Agent/src/info/novatec/inspectit/util/WeakList.java b/Agent/src/info/novatec/inspectit/util/WeakList.java new file mode 100644 index 000000000..4cf29eeee --- /dev/null +++ b/Agent/src/info/novatec/inspectit/util/WeakList.java @@ -0,0 +1,139 @@ +package info.novatec.inspectit.util; + +import java.lang.ref.SoftReference; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.List; + +/** + * This list is used to store elements as weak references. Especially useful to save a list of + * {@link ClassLoader} objects in a JEE environment. Objects are added and removed as in any other + * list, but can turn to become null. Calling {@link #removeAllNullElements()} is the only way to + * get rid of the garbage collected elements. {@link #getHardReferences()} returns an + * {@link ArrayList} suppressing all the references in this list which are already removed by the + * garbage collector. + * + * @param + * The class contained in the list. + * + * @author Patrice Bouillet + * + */ +public class WeakList extends AbstractList { + + /** + * Stores the weak references to the object. + */ + private List> refs = new ArrayList>(); + + /** + * Returns the hard reference. + * + * @param o + * The object. + * @return The reference to the object. + */ + private T getHardReference(SoftReference o) { + if (null != o) { + return o.get(); + } else { + return null; + } + } + + /** + * {@inheritDoc} + */ + public T get(int index) { + return getHardReference(refs.get(index)); + } + + /** + * {@inheritDoc} + */ + public boolean add(T o) { + return refs.add(new SoftReference(o)); + } + + /** + * {@inheritDoc} + */ + public void add(int index, T o) { + refs.add(index, new SoftReference(o)); + } + + /** + * {@inheritDoc} + */ + public void clear() { + refs.clear(); + } + + /** + * {@inheritDoc} + */ + public T remove(int index) { + return getHardReference(refs.remove(index)); + } + + /** + * {@inheritDoc} + */ + public T set(int index, T element) { + return getHardReference(refs.set(index, new SoftReference(element))); + } + + /** + * {@inheritDoc} + */ + public boolean contains(Object o) { + for (int i = 0; i < size(); i++) { + if (o == get(i)) { + return true; + } + } + return false; + } + + /** + * {@inheritDoc} + */ + public int size() { + return refs.size(); + } + + /** + * Returns a list of hard references. Skips all weak references but doesn't delete them if there + * are any. The list returned (especially the indices) aren't the same as the ones from this + * list. + * + * @return An {@link ArrayList} containing all the hard references of this weak list. + */ + public List getHardReferences() { + List result = new ArrayList(); + + for (int i = 0; i < size(); i++) { + T tmp = get(i); + + if (null != tmp) { + result.add(tmp); + } + } + + return result; + } + + /** + * Calling this method removes all the garbage collected elements in this list which appear to + * be null now.
+ * TODO: call this method periodically! + */ + public void removeAllNullElements() { + for (int i = size() - 1; i >= 0; i--) { + if (get(i) == null) { + remove(i); + } + } + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/AbstractLogSupport.java b/Agent/test/info/novatec/inspectit/agent/AbstractLogSupport.java new file mode 100644 index 000000000..6105a429f --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/AbstractLogSupport.java @@ -0,0 +1,37 @@ +package info.novatec.inspectit.agent; + +import java.io.IOException; +import java.util.logging.Level; + +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeSuite; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.util.StatusPrinter; + +/** + * This abstract class is used if the logging level needs to be changed. The default of + * {@link Level#INFO} is most of the time not used. + * + * @author Patrice Bouillet + * + */ +public abstract class AbstractLogSupport extends MockInit { + + /** + * Init logging. + */ + @BeforeSuite + public void initLogging() throws IOException { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + // don't print anything + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + context.reset(); + + StatusPrinter.printInCaseOfErrorsOrWarnings(context); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/MockInit.java b/Agent/test/info/novatec/inspectit/agent/MockInit.java new file mode 100644 index 000000000..9df248685 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/MockInit.java @@ -0,0 +1,13 @@ +package info.novatec.inspectit.agent; + +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; + +public abstract class MockInit { + + @BeforeMethod + public void initMocks() { + MockitoAnnotations.initMocks(this); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/AnnotationMatcherTest.java b/Agent/test/info/novatec/inspectit/agent/analyzer/AnnotationMatcherTest.java new file mode 100644 index 000000000..9ac58560e --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/AnnotationMatcherTest.java @@ -0,0 +1,284 @@ +package info.novatec.inspectit.agent.analyzer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.MockInit; +import info.novatec.inspectit.agent.analyzer.impl.AnnotationMatcher; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.NotFoundException; + +import org.mockito.Mock; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class AnnotationMatcherTest extends MockInit { + + @Mock + private IClassPoolAnalyzer classPoolAnalyzer; + + @Mock + private IInheritanceAnalyzer inheritanceAnalyzer; + + private UnregisteredSensorConfig unregisteredSensorConfig; + + private IMatcher matcher; + + private IMatcher delegateMatcher; + + public AnnotationMatcherTest() { + super(); + } + + @TestAnnotation + public AnnotationMatcherTest(int dummy) { + super(); + } + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + unregisteredSensorConfig = new UnregisteredSensorConfig(classPoolAnalyzer, inheritanceAnalyzer); + unregisteredSensorConfig.setIgnoreSignature(false); + unregisteredSensorConfig.setInterface(false); + unregisteredSensorConfig.setSuperclass(false); + unregisteredSensorConfig.setTargetPackageName(""); + unregisteredSensorConfig.setTargetClassName(this.getClass().getName()); + unregisteredSensorConfig.setTargetMethodName("*"); + unregisteredSensorConfig.setParameterTypes(Collections. emptyList()); + unregisteredSensorConfig.setPropertyAccess(false); + unregisteredSensorConfig.setSettings(Collections. emptyMap()); + + delegateMatcher = mock(IMatcher.class); + matcher = new AnnotationMatcher(inheritanceAnalyzer, classPoolAnalyzer, unregisteredSensorConfig, delegateMatcher); + } + + @SuppressWarnings("unchecked") + @Test + public void testAnnotatedMethods() throws NotFoundException { + unregisteredSensorConfig.setAnnotationClassName(TestAnnotation.class.getName()); + + ClassLoader classLoader = this.getClass().getClassLoader(); + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(this.getClass().getName()); + CtMethod[] ctMethods = ctClass.getDeclaredMethods(); + + when(delegateMatcher.getMatchingMethods(classLoader, this.getClass().getName())).thenReturn(Arrays.asList(ctMethods)); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + + Iterator iterator = mock(Iterator.class); + when(iterator.hasNext()).thenReturn(false); + when(inheritanceAnalyzer.getSuperclassIterator(classLoader, this.getClass().getName())).thenReturn(iterator); + when(inheritanceAnalyzer.getInterfaceIterator(classLoader, this.getClass().getName())).thenReturn(iterator); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, this.getClass().getName()); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, is(not(empty()))); + for (CtMethod method : ctMethodList) { + assertThat(method.hasAnnotation(TestAnnotation.class), is(true)); + } + } + + @SuppressWarnings("unchecked") + @Test + public void testAnnotatedConstructors() throws NotFoundException { + unregisteredSensorConfig.setAnnotationClassName(TestAnnotation.class.getName()); + + ClassLoader classLoader = this.getClass().getClassLoader(); + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(this.getClass().getName()); + CtConstructor[] ctConstructors = ctClass.getDeclaredConstructors(); + + when(delegateMatcher.getMatchingConstructors(classLoader, this.getClass().getName())).thenReturn(Arrays.asList(ctConstructors)); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + + Iterator iterator = mock(Iterator.class); + when(iterator.hasNext()).thenReturn(false); + when(inheritanceAnalyzer.getSuperclassIterator(classLoader, this.getClass().getName())).thenReturn(iterator); + when(inheritanceAnalyzer.getInterfaceIterator(classLoader, this.getClass().getName())).thenReturn(iterator); + + // execute the test call + List ctConstructorList = matcher.getMatchingConstructors(classLoader, this.getClass().getName()); + assertThat(ctConstructorList, is(notNullValue())); + assertThat(ctConstructorList, is(not(empty()))); + for (CtConstructor constructor : ctConstructorList) { + assertThat(constructor.hasAnnotation(TestAnnotation.class), is(true)); + } + } + + @SuppressWarnings("unchecked") + @Test + public void testAnnotatedClass() throws NotFoundException { + unregisteredSensorConfig.setAnnotationClassName(TestAnnotation.class.getName()); + + ClassLoader classLoader = this.getClass().getClassLoader(); + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(TestClass.class.getName()); + + // test the methods of annotated class (all should be loaded) + CtMethod[] ctMethods = ctClass.getDeclaredMethods(); + when(delegateMatcher.getMatchingMethods(classLoader, TestClass.class.getName())).thenReturn(Arrays.asList(ctMethods)); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + + Iterator iterator = mock(Iterator.class); + when(iterator.hasNext()).thenReturn(false); + when(inheritanceAnalyzer.getSuperclassIterator(classLoader, this.getClass().getName())).thenReturn(iterator); + when(inheritanceAnalyzer.getInterfaceIterator(classLoader, this.getClass().getName())).thenReturn(iterator); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, TestClass.class.getName()); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, is(not(empty()))); + assertThat(ctMethodList, hasSize(ctMethods.length)); + + // test the constructors of annotated class (all should be loaded) + CtConstructor[] ctConstructors = ctClass.getDeclaredConstructors(); + when(delegateMatcher.getMatchingConstructors(classLoader, TestClass.class.getName())).thenReturn(Arrays.asList(ctConstructors)); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + + // execute the test call + List ctConstructorList = matcher.getMatchingConstructors(classLoader, TestClass.class.getName()); + assertThat(ctConstructorList, is(notNullValue())); + assertThat(ctConstructorList, is(not(empty()))); + assertThat(ctConstructorList, hasSize(ctConstructors.length)); + } + + @Test + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void testSuperclassAnnotation() throws NotFoundException { + unregisteredSensorConfig.setAnnotationClassName(TestAnnotation.class.getName()); + + ClassLoader classLoader = this.getClass().getClassLoader(); + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(ExtendedTestClass.class.getName()); + + // test the methods of annotated class (all should be loaded) + CtMethod[] ctMethods = ctClass.getDeclaredMethods(); + when(delegateMatcher.getMatchingMethods(classLoader, ExtendedTestClass.class.getName())).thenReturn(Arrays.asList(ctMethods)); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + + Iterator iterator = mock(Iterator.class); + when(iterator.hasNext()).thenReturn(true).thenReturn(false); + when(iterator.next()).thenReturn(classPool.get(ExtendedTestClass.class.getSuperclass().getName())); + when(inheritanceAnalyzer.getSuperclassIterator(classLoader, ExtendedTestClass.class.getName())).thenReturn(iterator); + when(inheritanceAnalyzer.getInterfaceIterator(classLoader, ExtendedTestClass.class.getName())).thenReturn(new ArrayList().iterator()); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, ExtendedTestClass.class.getName()); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, is(not(empty()))); + assertThat(ctMethodList, hasSize(ctMethods.length)); + + // test the constructors of annotated class (all should be loaded) + CtConstructor[] ctConstructors = ctClass.getDeclaredConstructors(); + when(delegateMatcher.getMatchingConstructors(classLoader, ExtendedTestClass.class.getName())).thenReturn(Arrays.asList(ctConstructors)); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + + iterator = mock(Iterator.class); + when(iterator.hasNext()).thenReturn(true).thenReturn(false); + when(iterator.next()).thenReturn(classPool.get(ExtendedTestClass.class.getSuperclass().getName())); + when(inheritanceAnalyzer.getSuperclassIterator(classLoader, ExtendedTestClass.class.getName())).thenReturn(iterator); + when(inheritanceAnalyzer.getInterfaceIterator(classLoader, ExtendedTestClass.class.getName())).thenReturn(new ArrayList().iterator()); + + // execute the test call + List ctConstructorList = matcher.getMatchingConstructors(classLoader, ExtendedTestClass.class.getName()); + assertThat(ctConstructorList, is(notNullValue())); + assertThat(ctConstructorList, is(not(empty()))); + assertThat(ctConstructorList, hasSize(ctConstructors.length)); + } + + @Test + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void testInterfaceAnnotation() throws NotFoundException { + unregisteredSensorConfig.setAnnotationClassName(TestAnnotation.class.getName()); + + ClassLoader classLoader = this.getClass().getClassLoader(); + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(InterfaceImplTest.class.getName()); + + // test the methods of annotated class (all should be loaded) + CtMethod[] ctMethods = ctClass.getDeclaredMethods(); + when(delegateMatcher.getMatchingMethods(classLoader, InterfaceImplTest.class.getName())).thenReturn(Arrays.asList(ctMethods)); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + + Iterator iterator = mock(Iterator.class); + when(iterator.hasNext()).thenReturn(true).thenReturn(false); + when(iterator.next()).thenReturn(classPool.get(TestInterface.class.getName())); + when(inheritanceAnalyzer.getSuperclassIterator(classLoader, InterfaceImplTest.class.getName())).thenReturn(new ArrayList().iterator()); + when(inheritanceAnalyzer.getInterfaceIterator(classLoader, InterfaceImplTest.class.getName())).thenReturn(iterator); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, InterfaceImplTest.class.getName()); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, is(not(empty()))); + assertThat(ctMethodList, hasSize(ctMethods.length)); + + // test the constructors of annotated class (all should be loaded) + CtConstructor[] ctConstructors = ctClass.getDeclaredConstructors(); + when(delegateMatcher.getMatchingConstructors(classLoader, InterfaceImplTest.class.getName())).thenReturn(Arrays.asList(ctConstructors)); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + + iterator = mock(Iterator.class); + when(iterator.hasNext()).thenReturn(true).thenReturn(false); + when(iterator.next()).thenReturn(classPool.get(TestInterface.class.getName())); + when(inheritanceAnalyzer.getSuperclassIterator(classLoader, InterfaceImplTest.class.getName())).thenReturn(new ArrayList().iterator()); + when(inheritanceAnalyzer.getInterfaceIterator(classLoader, InterfaceImplTest.class.getName())).thenReturn(iterator); + + // execute the test call + List ctConstructorList = matcher.getMatchingConstructors(classLoader, InterfaceImplTest.class.getName()); + assertThat(ctConstructorList, is(notNullValue())); + assertThat(ctConstructorList, is(not(empty()))); + assertThat(ctConstructorList, hasSize(ctConstructors.length)); + } + + public static @interface TestAnnotation { + + } + + @TestAnnotation + public void testMethod() { + + } + + @TestAnnotation + public static class TestClass { + + public void dummyMethod() { + } + + } + + @TestAnnotation + public interface TestInterface { + + } + + public static class ExtendedTestClass extends TestClass { + + public void dummyMethod() { + } + } + + public static class InterfaceImplTest implements TestInterface { + + public void dummyMethod() { + } + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/DirectMatcherTest.java b/Agent/test/info/novatec/inspectit/agent/analyzer/DirectMatcherTest.java new file mode 100644 index 000000000..ed4114d2d --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/DirectMatcherTest.java @@ -0,0 +1,158 @@ +package info.novatec.inspectit.agent.analyzer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.MockInit; +import info.novatec.inspectit.agent.analyzer.impl.DirectMatcher; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; + +import java.util.Collections; +import java.util.List; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; + +import org.mockito.Mock; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class DirectMatcherTest extends MockInit { + + @Mock + private IClassPoolAnalyzer classPoolAnalyzer; + + private UnregisteredSensorConfig unregisteredSensorConfig; + + private IMatcher matcher; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + unregisteredSensorConfig = new UnregisteredSensorConfig(classPoolAnalyzer, mock(IInheritanceAnalyzer.class)); + unregisteredSensorConfig.setIgnoreSignature(false); + unregisteredSensorConfig.setInterface(false); + unregisteredSensorConfig.setSuperclass(false); + unregisteredSensorConfig.setTargetPackageName(""); + unregisteredSensorConfig.setTargetClassName("info.novatec.test.Test"); + unregisteredSensorConfig.setTargetMethodName("testMethod"); + unregisteredSensorConfig.setParameterTypes(Collections. emptyList()); + unregisteredSensorConfig.setPropertyAccess(false); + unregisteredSensorConfig.setSettings(Collections. emptyMap()); + + matcher = new DirectMatcher(classPoolAnalyzer, unregisteredSensorConfig); + } + + @Test + public void compareClassName() throws NotFoundException { + boolean compareResult = matcher.compareClassName(this.getClass().getClassLoader(), "info.novatec.test.Test"); + assertThat(compareResult, is(true)); + + verifyZeroInteractions(classPoolAnalyzer); + } + + @Test + public void failCompareClassName() throws NotFoundException { + boolean compareResult = matcher.compareClassName(this.getClass().getClassLoader(), "info.novatec.test.Fail"); + assertThat(compareResult, is(false)); + + verifyZeroInteractions(classPoolAnalyzer); + } + + @Test + public void emptyClassName() throws NotFoundException { + boolean compareResult = matcher.compareClassName(this.getClass().getClassLoader(), ""); + assertThat(compareResult, is(false)); + + verifyZeroInteractions(classPoolAnalyzer); + } + + @Test + public void regexClassName() throws NotFoundException { + boolean compareResult = matcher.compareClassName(this.getClass().getClassLoader(), "*"); + assertThat(compareResult, is(false)); + + verifyZeroInteractions(classPoolAnalyzer); + } + + public void testMethod() { + } + + public void testMethod(String msg) { + } + + @Test + public void getMatchingMethods() throws NotFoundException { + ClassLoader classLoader = this.getClass().getClassLoader(); + CtMethod[] ctMethods = new CtMethod[2]; + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(this.getClass().getName()); + ctMethods[0] = ctClass.getDeclaredMethod("testMethod", null); + ctMethods[1] = ctClass.getDeclaredMethod("testMethod", new CtClass[] { classPool.get("java.lang.String") }); + + // stub the getMethodsForClassName method + when(classPoolAnalyzer.getMethodsForClassName(classLoader, "info.novatec.test.Test")).thenReturn(ctMethods); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + matcher.checkParameters(ctMethodList); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, hasSize(1)); + assertThat(ctMethodList.get(0).getParameterTypes().length, is(equalTo(0))); + + verify(classPoolAnalyzer, times(1)).getMethodsForClassName(classLoader, "info.novatec.test.Test"); + verifyNoMoreInteractions(classPoolAnalyzer); + } + + @Test + public void getMatchingMethodsIgnoreSignature() throws NotFoundException { + // ignore the signature, now the result should contain two methods + unregisteredSensorConfig.setIgnoreSignature(true); + + ClassLoader classLoader = this.getClass().getClassLoader(); + CtMethod[] ctMethods = new CtMethod[2]; + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(this.getClass().getName()); + ctMethods[0] = ctClass.getDeclaredMethod("testMethod", null); + ctMethods[1] = ctClass.getDeclaredMethod("testMethod", new CtClass[] { classPool.get("java.lang.String") }); + + // stub the getMethodsForClassName method + when(classPoolAnalyzer.getMethodsForClassName(classLoader, "info.novatec.test.Test")).thenReturn(ctMethods); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, hasSize(2)); + + verify(classPoolAnalyzer, times(1)).getMethodsForClassName(classLoader, "info.novatec.test.Test"); + verifyNoMoreInteractions(classPoolAnalyzer); + } + + @Test + public void getMatchingMethodsNoMethods() throws NotFoundException { + ClassLoader classLoader = this.getClass().getClassLoader(); + CtMethod[] ctMethods = new CtMethod[0]; + + // stub the getMethodsForClassName method + when(classPoolAnalyzer.getMethodsForClassName(classLoader, "info.novatec.test.Test")).thenReturn(ctMethods); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, is(empty())); + + verify(classPoolAnalyzer, times(1)).getMethodsForClassName(classLoader, "info.novatec.test.Test"); + verifyNoMoreInteractions(classPoolAnalyzer); + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/IndirectMatcherTest.java b/Agent/test/info/novatec/inspectit/agent/analyzer/IndirectMatcherTest.java new file mode 100644 index 000000000..6ca8cb2b1 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/IndirectMatcherTest.java @@ -0,0 +1,193 @@ +package info.novatec.inspectit.agent.analyzer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.MockInit; +import info.novatec.inspectit.agent.analyzer.impl.IndirectMatcher; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; + +import org.mockito.Mock; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class IndirectMatcherTest extends MockInit { + + @Mock + private IClassPoolAnalyzer classPoolAnalyzer; + + private UnregisteredSensorConfig unregisteredSensorConfig; + + private IMatcher matcher; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + unregisteredSensorConfig = new UnregisteredSensorConfig(classPoolAnalyzer, mock(IInheritanceAnalyzer.class)); + unregisteredSensorConfig.setIgnoreSignature(false); + unregisteredSensorConfig.setInterface(false); + unregisteredSensorConfig.setSuperclass(false); + unregisteredSensorConfig.setTargetPackageName(""); + unregisteredSensorConfig.setTargetClassName("info.novatec.test.*"); + unregisteredSensorConfig.setTargetMethodName("t*Method"); + List parameterList = new ArrayList(); + parameterList.add("*String"); + unregisteredSensorConfig.setParameterTypes(parameterList); + unregisteredSensorConfig.setPropertyAccess(false); + unregisteredSensorConfig.setSettings(Collections. emptyMap()); + + matcher = new IndirectMatcher(classPoolAnalyzer, unregisteredSensorConfig); + } + + @Test + public void compareClassName() throws NotFoundException { + boolean compareResult = matcher.compareClassName(this.getClass().getClassLoader(), "info.novatec.test.Test"); + assertThat(compareResult, is(true)); + + verifyZeroInteractions(classPoolAnalyzer); + } + + @Test + public void failCompareClassName() throws NotFoundException { + boolean compareResult = matcher.compareClassName(this.getClass().getClassLoader(), "info.novatec.fail.Test"); + assertThat(compareResult, is(false)); + + verifyZeroInteractions(classPoolAnalyzer); + } + + @Test + public void emptyClassName() throws NotFoundException { + boolean compareResult = matcher.compareClassName(this.getClass().getClassLoader(), ""); + assertThat(compareResult, is(false)); + + verifyZeroInteractions(classPoolAnalyzer); + } + + @Test + public void regexClassName() throws NotFoundException { + boolean compareResult = matcher.compareClassName(this.getClass().getClassLoader(), "*"); + assertThat(compareResult, is(false)); + + verifyZeroInteractions(classPoolAnalyzer); + } + + public void testMethod() { + } + + public void testMethod(String msg) { + } + + @Test + public void getMatchingMethods() throws NotFoundException { + ClassLoader classLoader = this.getClass().getClassLoader(); + CtMethod[] ctMethods = new CtMethod[2]; + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(this.getClass().getName()); + ctMethods[0] = ctClass.getDeclaredMethod("testMethod", null); + ctMethods[1] = ctClass.getDeclaredMethod("testMethod", new CtClass[] { classPool.get("java.lang.String") }); + + // stub the getMethodsForClassName method + when(classPoolAnalyzer.getMethodsForClassName(classLoader, "info.novatec.test.Test")).thenReturn(ctMethods); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + matcher.checkParameters(ctMethodList); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, hasSize(1)); + assertThat(ctMethodList.get(0).getParameterTypes().length, is(equalTo(1))); + assertThat(ctMethodList.get(0).getParameterTypes()[0].getName(), is(equalTo("java.lang.String"))); + + verify(classPoolAnalyzer, times(1)).getMethodsForClassName(classLoader, "info.novatec.test.Test"); + verifyNoMoreInteractions(classPoolAnalyzer); + } + + @SuppressWarnings("unchecked") + @Test + public void getMatchingMethodsNoParameter() throws NotFoundException { + // no parameters for this test + unregisteredSensorConfig.setParameterTypes(Collections.EMPTY_LIST); + + ClassLoader classLoader = this.getClass().getClassLoader(); + CtMethod[] ctMethods = new CtMethod[2]; + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(this.getClass().getName()); + ctMethods[0] = ctClass.getDeclaredMethod("testMethod", null); + ctMethods[1] = ctClass.getDeclaredMethod("testMethod", new CtClass[] { classPool.get("java.lang.String") }); + + // stub the getMethodsForClassName method + when(classPoolAnalyzer.getMethodsForClassName(classLoader, "info.novatec.test.Test")).thenReturn(ctMethods); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + matcher.checkParameters(ctMethodList); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, hasSize(1)); + assertThat(ctMethodList.get(0).getParameterTypes().length, is(equalTo(0))); + + verify(classPoolAnalyzer, times(1)).getMethodsForClassName(classLoader, "info.novatec.test.Test"); + verifyNoMoreInteractions(classPoolAnalyzer); + } + + @Test + public void getMatchingMethodsIgnoreSignature() throws NotFoundException { + // ignore the signature, now the result should contain two methods + unregisteredSensorConfig.setIgnoreSignature(true); + + ClassLoader classLoader = this.getClass().getClassLoader(); + CtMethod[] ctMethods = new CtMethod[2]; + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(this.getClass().getName()); + ctMethods[0] = ctClass.getDeclaredMethod("testMethod", null); + ctMethods[1] = ctClass.getDeclaredMethod("testMethod", new CtClass[] { classPool.get("java.lang.String") }); + + // stub the getMethodsForClassName method + when(classPoolAnalyzer.getMethodsForClassName(classLoader, "info.novatec.test.Test")).thenReturn(ctMethods); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, hasSize(2)); + assertThat(ctMethodList.get(0).getParameterTypes().length, is(equalTo(0))); + assertThat(ctMethodList.get(1).getParameterTypes().length, is(equalTo(1))); + assertThat(ctMethodList.get(1).getParameterTypes()[0].getName(), is(equalTo("java.lang.String"))); + + verify(classPoolAnalyzer, times(1)).getMethodsForClassName(classLoader, "info.novatec.test.Test"); + verifyNoMoreInteractions(classPoolAnalyzer); + } + + @Test + public void getMatchingMethodsNoMethods() throws NotFoundException { + ClassLoader classLoader = this.getClass().getClassLoader(); + CtMethod[] ctMethods = new CtMethod[0]; + + // stub the getMethodsForClassName method + when(classPoolAnalyzer.getMethodsForClassName(classLoader, "info.novatec.test.Test")).thenReturn(ctMethods); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, is(empty())); + + verify(classPoolAnalyzer, times(1)).getMethodsForClassName(classLoader, "info.novatec.test.Test"); + verifyNoMoreInteractions(classPoolAnalyzer); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/InterfaceMatcherTest.java b/Agent/test/info/novatec/inspectit/agent/analyzer/InterfaceMatcherTest.java new file mode 100644 index 000000000..eb31f8156 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/InterfaceMatcherTest.java @@ -0,0 +1,205 @@ +package info.novatec.inspectit.agent.analyzer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.MockInit; +import info.novatec.inspectit.agent.analyzer.impl.InterfaceMatcher; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; + +import org.mockito.Mock; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class InterfaceMatcherTest extends MockInit { + + @Mock + private IClassPoolAnalyzer classPoolAnalyzer; + + @Mock + private IInheritanceAnalyzer inheritanceAnalyzer; + + private UnregisteredSensorConfig unregisteredSensorConfig; + + private IMatcher matcher; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + unregisteredSensorConfig = new UnregisteredSensorConfig(classPoolAnalyzer, mock(IInheritanceAnalyzer.class)); + unregisteredSensorConfig.setIgnoreSignature(false); + unregisteredSensorConfig.setInterface(true); + unregisteredSensorConfig.setSuperclass(false); + unregisteredSensorConfig.setTargetPackageName(""); + unregisteredSensorConfig.setTargetClassName("info.novatec.test.ITest"); + unregisteredSensorConfig.setTargetMethodName("testMethod"); + List parameterList = new ArrayList(); + parameterList.add("java.lang.String"); + unregisteredSensorConfig.setParameterTypes(parameterList); + unregisteredSensorConfig.setPropertyAccess(false); + unregisteredSensorConfig.setSettings(Collections. emptyMap()); + + matcher = new InterfaceMatcher(inheritanceAnalyzer, classPoolAnalyzer, unregisteredSensorConfig); + } + + @SuppressWarnings("unchecked") + @Test + public void compareClassName() throws NotFoundException { + ClassLoader classLoader = this.getClass().getClassLoader(); + Iterator iterator = mock(Iterator.class); + when(iterator.hasNext()).thenReturn(true).thenReturn(false); + CtClass ctClass = mock(CtClass.class); + when(ctClass.getName()).thenReturn("info.novatec.test.ITest"); + when(iterator.next()).thenReturn(ctClass); + + String className = "info.novatec.test.Test"; + when(inheritanceAnalyzer.getInterfaceIterator(classLoader, className)).thenReturn(iterator); + boolean compareResult = matcher.compareClassName(this.getClass().getClassLoader(), className); + assertThat(compareResult, is(true)); + + verifyZeroInteractions(classPoolAnalyzer); + verify(inheritanceAnalyzer, times(1)).getInterfaceIterator(classLoader, className); + verifyNoMoreInteractions(inheritanceAnalyzer); + } + + @SuppressWarnings("unchecked") + @Test + public void compareClassNameVirtual() throws NotFoundException { + unregisteredSensorConfig.setVirtual(true); + unregisteredSensorConfig.setTargetClassName("info.novatec.test.*"); + // needed as the virtual setting is checked in the constructor of the + // interface matcher + matcher = new InterfaceMatcher(inheritanceAnalyzer, classPoolAnalyzer, unregisteredSensorConfig); + + ClassLoader classLoader = this.getClass().getClassLoader(); + Iterator iterator = mock(Iterator.class); + when(iterator.hasNext()).thenReturn(true).thenReturn(false); + CtClass ctClass = mock(CtClass.class); + when(ctClass.getName()).thenReturn("info.novatec.test.ITest"); + when(iterator.next()).thenReturn(ctClass); + + String className = "info.novatec.test.Test"; + when(inheritanceAnalyzer.getInterfaceIterator(classLoader, className)).thenReturn(iterator); + boolean compareResult = matcher.compareClassName(this.getClass().getClassLoader(), className); + assertThat(compareResult, is(true)); + + verifyZeroInteractions(classPoolAnalyzer); + verify(inheritanceAnalyzer, times(1)).getInterfaceIterator(classLoader, className); + verifyNoMoreInteractions(inheritanceAnalyzer); + } + + @SuppressWarnings("unchecked") + @Test + public void failCompareClassName() throws NotFoundException { + ClassLoader classLoader = this.getClass().getClassLoader(); + Iterator iterator = mock(Iterator.class); + when(iterator.hasNext()).thenReturn(false); + + when(inheritanceAnalyzer.getInterfaceIterator(eq(classLoader), anyString())).thenReturn(iterator); + boolean compareResult = matcher.compareClassName(classLoader, "info.novatec.test.Fail"); + assertThat(compareResult, is(false)); + compareResult = matcher.compareClassName(classLoader, "info.novatec.Fail"); + assertThat(compareResult, is(false)); + compareResult = matcher.compareClassName(classLoader, "info.novatec.*"); + assertThat(compareResult, is(false)); + compareResult = matcher.compareClassName(classLoader, ""); + assertThat(compareResult, is(false)); + + verifyZeroInteractions(classPoolAnalyzer); + verify(inheritanceAnalyzer, times(4)).getInterfaceIterator(eq(classLoader), anyString()); + verifyNoMoreInteractions(inheritanceAnalyzer); + } + + public void testMethod() { + } + + public void testMethod(String msg) { + } + + @Test + public void getMatchingMethods() throws NotFoundException { + ClassLoader classLoader = this.getClass().getClassLoader(); + CtMethod[] ctMethods = new CtMethod[2]; + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(this.getClass().getName()); + ctMethods[0] = ctClass.getDeclaredMethod("testMethod", null); + ctMethods[1] = ctClass.getDeclaredMethod("testMethod", new CtClass[] { classPool.get("java.lang.String") }); + + // stub the getMethodsForClassName method + when(classPoolAnalyzer.getMethodsForClassName(classLoader, "info.novatec.test.Test")).thenReturn(ctMethods); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + matcher.checkParameters(ctMethodList); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, hasSize(1)); + assertThat(ctMethodList.get(0).getParameterTypes().length, is(equalTo(1))); + assertThat(ctMethodList.get(0).getParameterTypes()[0].getName(), is(equalTo("java.lang.String"))); + + verify(classPoolAnalyzer, times(1)).getMethodsForClassName(classLoader, "info.novatec.test.Test"); + verifyNoMoreInteractions(classPoolAnalyzer); + } + + @Test + public void getMatchingMethodsIgnoreSignature() throws NotFoundException { + // ignore the signature, now the result should contain two methods + unregisteredSensorConfig.setIgnoreSignature(true); + + ClassLoader classLoader = this.getClass().getClassLoader(); + CtMethod[] ctMethods = new CtMethod[2]; + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(this.getClass().getName()); + ctMethods[0] = ctClass.getDeclaredMethod("testMethod", null); + ctMethods[1] = ctClass.getDeclaredMethod("testMethod", new CtClass[] { classPool.get("java.lang.String") }); + + // stub the getMethodsForClassName method + when(classPoolAnalyzer.getMethodsForClassName(classLoader, "info.novatec.test.Test")).thenReturn(ctMethods); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, hasSize(2)); + + verify(classPoolAnalyzer, times(1)).getMethodsForClassName(classLoader, "info.novatec.test.Test"); + verifyNoMoreInteractions(classPoolAnalyzer); + } + + @Test + public void getMatchingMethodsNoMethods() throws NotFoundException { + ClassLoader classLoader = this.getClass().getClassLoader(); + CtMethod[] ctMethods = new CtMethod[0]; + + // stub the getMethodsForClassName method + when(classPoolAnalyzer.getMethodsForClassName(classLoader, "info.novatec.test.Test")).thenReturn(ctMethods); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, is(empty())); + + verify(classPoolAnalyzer, times(1)).getMethodsForClassName(classLoader, "info.novatec.test.Test"); + verifyNoMoreInteractions(classPoolAnalyzer); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/ModifierMatcherTest.java b/Agent/test/info/novatec/inspectit/agent/analyzer/ModifierMatcherTest.java new file mode 100644 index 000000000..fe23286a0 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/ModifierMatcherTest.java @@ -0,0 +1,210 @@ +package info.novatec.inspectit.agent.analyzer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.MockInit; +import info.novatec.inspectit.agent.analyzer.impl.ModifierMatcher; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.Modifier; +import javassist.NotFoundException; + +import org.mockito.Mock; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class ModifierMatcherTest extends MockInit { + + @Mock + private IClassPoolAnalyzer classPoolAnalyzer; + + private UnregisteredSensorConfig unregisteredSensorConfig; + + private IMatcher matcher; + + private IMatcher delegateMatcher; + + public ModifierMatcherTest() { + super(); + } + + @SuppressWarnings("unused") + private ModifierMatcherTest(int dummy) { + super(); + } + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + unregisteredSensorConfig = new UnregisteredSensorConfig(classPoolAnalyzer, mock(IInheritanceAnalyzer.class)); + unregisteredSensorConfig.setIgnoreSignature(false); + unregisteredSensorConfig.setInterface(false); + unregisteredSensorConfig.setSuperclass(false); + unregisteredSensorConfig.setTargetPackageName(""); + unregisteredSensorConfig.setTargetClassName("info.novatec.test.Test"); + unregisteredSensorConfig.setTargetMethodName("t*Method"); + unregisteredSensorConfig.setParameterTypes(Collections. emptyList()); + unregisteredSensorConfig.setPropertyAccess(false); + unregisteredSensorConfig.setSettings(Collections. emptyMap()); + + delegateMatcher = mock(IMatcher.class); + matcher = new ModifierMatcher(classPoolAnalyzer, unregisteredSensorConfig, delegateMatcher); + } + + @SuppressWarnings("unused") + private void testPrivateMethod() { + } + + protected void testProtectedMethod() { + } + + public void testPublicMethod() { + } + + void testDefaultMethod() { + } + + @Test + public void testPrivate() throws NotFoundException { + unregisteredSensorConfig.setModifiers(Modifier.PRIVATE); + + ClassLoader classLoader = this.getClass().getClassLoader(); + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(this.getClass().getName()); + CtMethod[] ctMethods = ctClass.getDeclaredMethods(); + + // stub the delegateMatcher method + when(delegateMatcher.getMatchingMethods(classLoader, "info.novatec.test.Test")).thenReturn(Arrays.asList(ctMethods)); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, is(not(empty()))); + for (CtMethod method : ctMethodList) { + assertThat(Modifier.isPrivate(method.getModifiers()), is(true)); + } + } + + @Test + public void testProtected() throws NotFoundException { + unregisteredSensorConfig.setModifiers(Modifier.PROTECTED); + + ClassLoader classLoader = this.getClass().getClassLoader(); + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(this.getClass().getName()); + CtMethod[] ctMethods = ctClass.getDeclaredMethods(); + + // stub the delegateMatcher method + when(delegateMatcher.getMatchingMethods(classLoader, "info.novatec.test.Test")).thenReturn(Arrays.asList(ctMethods)); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, is(not(empty()))); + for (CtMethod method : ctMethodList) { + assertThat(Modifier.isProtected(method.getModifiers()), is(true)); + } + + } + + @Test + public void testPublic() throws NotFoundException { + unregisteredSensorConfig.setModifiers(Modifier.PUBLIC); + + ClassLoader classLoader = this.getClass().getClassLoader(); + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(this.getClass().getName()); + CtMethod[] ctMethods = ctClass.getDeclaredMethods(); + + // stub the delegateMatcher method + when(delegateMatcher.getMatchingMethods(classLoader, "info.novatec.test.Test")).thenReturn(Arrays.asList(ctMethods)); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, is(not(empty()))); + for (CtMethod method : ctMethodList) { + assertThat(Modifier.isPublic(method.getModifiers()), is(true)); + } + + } + + @Test + public void testDefault() throws NotFoundException { + unregisteredSensorConfig.setModifiers(ModifierMatcher.DEFAULT); + + ClassLoader classLoader = this.getClass().getClassLoader(); + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(this.getClass().getName()); + CtMethod[] ctMethods = ctClass.getDeclaredMethods(); + + // stub the delegateMatcher method + when(delegateMatcher.getMatchingMethods(classLoader, "info.novatec.test.Test")).thenReturn(Arrays.asList(ctMethods)); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, is(not(empty()))); + for (CtMethod method : ctMethodList) { + assertThat(Modifier.isPackage(method.getModifiers()), is(true)); + } + + } + + @Test + public void testCombined() throws NotFoundException { + unregisteredSensorConfig.setModifiers(Modifier.PRIVATE | Modifier.PROTECTED); + + ClassLoader classLoader = this.getClass().getClassLoader(); + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(this.getClass().getName()); + CtMethod[] ctMethods = ctClass.getDeclaredMethods(); + + // stub the delegateMatcher method + when(delegateMatcher.getMatchingMethods(classLoader, "info.novatec.test.Test")).thenReturn(Arrays.asList(ctMethods)); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, is(not(empty()))); + for (CtMethod method : ctMethodList) { + assertThat(Modifier.isPrivate(method.getModifiers()) || Modifier.isProtected(method.getModifiers()), is(true)); + } + + } + + @Test + public void testConstructor() throws NotFoundException { + unregisteredSensorConfig.setModifiers(Modifier.PRIVATE); + + ClassLoader classLoader = this.getClass().getClassLoader(); + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(this.getClass().getName()); + CtConstructor[] ctConstructors = ctClass.getDeclaredConstructors(); + + // stub the delegateMatcher method + when(delegateMatcher.getMatchingConstructors(classLoader, "info.novatec.test.Test")).thenReturn(Arrays.asList(ctConstructors)); + + // execute the test call + List ctConstructorList = matcher.getMatchingConstructors(classLoader, "info.novatec.test.Test"); + assertThat(ctConstructorList, is(notNullValue())); + assertThat(ctConstructorList, is(not(empty()))); + for (CtConstructor constructor : ctConstructorList) { + assertThat(Modifier.isPrivate(constructor.getModifiers()), is(true)); + } + + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/SimpleMatchPatternTest.java b/Agent/test/info/novatec/inspectit/agent/analyzer/SimpleMatchPatternTest.java new file mode 100644 index 000000000..b72ab468a --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/SimpleMatchPatternTest.java @@ -0,0 +1,70 @@ +package info.novatec.inspectit.agent.analyzer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import info.novatec.inspectit.agent.analyzer.impl.SimpleMatchPattern; + +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class SimpleMatchPatternTest { + + @Test + public void trailingPattern() { + SimpleMatchPattern matchPattern = new SimpleMatchPattern("*test"); + assertThat(matchPattern.match("testtest"), is(true)); + assertThat(matchPattern.match("123test"), is(true)); + assertThat(matchPattern.match("test"), is(true)); + assertThat(matchPattern.match("hello"), is(false)); + assertThat(matchPattern.match(""), is(false)); + } + + @Test + public void middlePattern() { + SimpleMatchPattern matchPattern = new SimpleMatchPattern("test*"); + assertThat(matchPattern.match("testtest"), is(true)); + assertThat(matchPattern.match("test123"), is(true)); + assertThat(matchPattern.match("test"), is(true)); + assertThat(matchPattern.match("hello"), is(false)); + assertThat(matchPattern.match(""), is(false)); + } + + @Test + public void leadingPattern() { + SimpleMatchPattern matchPattern = new SimpleMatchPattern("*test*"); + assertThat(matchPattern.match("testtesttest"), is(true)); + assertThat(matchPattern.match("testtest"), is(true)); + assertThat(matchPattern.match("123test123"), is(true)); + assertThat(matchPattern.match("test123"), is(true)); + assertThat(matchPattern.match("test"), is(true)); + assertThat(matchPattern.match("hello"), is(false)); + assertThat(matchPattern.match(""), is(false)); + } + + @Test + public void mixedPattern() { + SimpleMatchPattern matchPattern = new SimpleMatchPattern("test*hello*world"); + assertThat(matchPattern.match("test1hello2world"), is(true)); + assertThat(matchPattern.match("testhelloworld"), is(true)); + assertThat(matchPattern.match("test123helloworld"), is(true)); + assertThat(matchPattern.match("hello"), is(false)); + assertThat(matchPattern.match(""), is(false)); + } + + @Test + public void everythingPattern() { + SimpleMatchPattern matchPattern = new SimpleMatchPattern("*"); + assertThat(matchPattern.match("test1hello2world"), is(true)); + assertThat(matchPattern.match("testhelloworld"), is(true)); + assertThat(matchPattern.match("test123helloworld"), is(true)); + assertThat(matchPattern.match("hello"), is(true)); + assertThat(matchPattern.match(""), is(true)); + } + + @Test + public void enhancedTests() { + SimpleMatchPattern matchPattern = new SimpleMatchPattern("vsa.nprod.stamm.priv.regelstruktur.server.logic.*.*Evaluator"); + assertThat(matchPattern.match("vsa.nprod.stamm.priv.regelstruktur.server.logic.rechnungssplitting.RechnungsSplittingEvaluator"), is(true)); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/SuperclassMatcherTest.java b/Agent/test/info/novatec/inspectit/agent/analyzer/SuperclassMatcherTest.java new file mode 100644 index 000000000..d2622e144 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/SuperclassMatcherTest.java @@ -0,0 +1,205 @@ +package info.novatec.inspectit.agent.analyzer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.MockInit; +import info.novatec.inspectit.agent.analyzer.impl.SuperclassMatcher; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; + +import org.mockito.Mock; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class SuperclassMatcherTest extends MockInit { + + @Mock + private IClassPoolAnalyzer classPoolAnalyzer; + + @Mock + private IInheritanceAnalyzer inheritanceAnalyzer; + + private UnregisteredSensorConfig unregisteredSensorConfig; + + private IMatcher matcher; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + unregisteredSensorConfig = new UnregisteredSensorConfig(classPoolAnalyzer, mock(IInheritanceAnalyzer.class)); + unregisteredSensorConfig.setIgnoreSignature(false); + unregisteredSensorConfig.setInterface(false); + unregisteredSensorConfig.setSuperclass(true); + unregisteredSensorConfig.setTargetPackageName(""); + unregisteredSensorConfig.setTargetClassName("info.novatec.test.AbstractTest"); + unregisteredSensorConfig.setTargetMethodName("testMethod"); + List parameterList = new ArrayList(); + parameterList.add("java.lang.String"); + unregisteredSensorConfig.setParameterTypes(parameterList); + unregisteredSensorConfig.setPropertyAccess(false); + unregisteredSensorConfig.setSettings(Collections. emptyMap()); + + matcher = new SuperclassMatcher(inheritanceAnalyzer, classPoolAnalyzer, unregisteredSensorConfig); + } + + @SuppressWarnings("unchecked") + @Test + public void compareClassName() throws NotFoundException { + ClassLoader classLoader = this.getClass().getClassLoader(); + Iterator iterator = mock(Iterator.class); + when(iterator.hasNext()).thenReturn(true).thenReturn(false); + CtClass ctClass = mock(CtClass.class); + when(ctClass.getName()).thenReturn("info.novatec.test.AbstractTest"); + when(iterator.next()).thenReturn(ctClass); + + String className = "info.novatec.test.Test"; + when(inheritanceAnalyzer.getSuperclassIterator(classLoader, className)).thenReturn(iterator); + boolean compareResult = matcher.compareClassName(this.getClass().getClassLoader(), className); + assertThat(compareResult, is(true)); + + verifyZeroInteractions(classPoolAnalyzer); + verify(inheritanceAnalyzer, times(1)).getSuperclassIterator(classLoader, className); + verifyNoMoreInteractions(inheritanceAnalyzer); + } + + @SuppressWarnings("unchecked") + @Test + public void compareClassNameVirtual() throws NotFoundException { + unregisteredSensorConfig.setVirtual(true); + unregisteredSensorConfig.setTargetClassName("info.novatec.test.*"); + // needed as the virtual setting is checked in the constructor of the + // interface matcher + matcher = new SuperclassMatcher(inheritanceAnalyzer, classPoolAnalyzer, unregisteredSensorConfig); + + ClassLoader classLoader = this.getClass().getClassLoader(); + Iterator iterator = mock(Iterator.class); + when(iterator.hasNext()).thenReturn(true).thenReturn(false); + CtClass ctClass = mock(CtClass.class); + when(ctClass.getName()).thenReturn("info.novatec.test.AbstractTest"); + when(iterator.next()).thenReturn(ctClass); + + String className = "info.novatec.test.Test"; + when(inheritanceAnalyzer.getSuperclassIterator(classLoader, className)).thenReturn(iterator); + boolean compareResult = matcher.compareClassName(this.getClass().getClassLoader(), className); + assertThat(compareResult, is(true)); + + verifyZeroInteractions(classPoolAnalyzer); + verify(inheritanceAnalyzer, times(1)).getSuperclassIterator(classLoader, className); + verifyNoMoreInteractions(inheritanceAnalyzer); + } + + @SuppressWarnings("unchecked") + @Test + public void failCompareClassName() throws NotFoundException { + ClassLoader classLoader = this.getClass().getClassLoader(); + Iterator iterator = mock(Iterator.class); + when(iterator.hasNext()).thenReturn(false); + + when(inheritanceAnalyzer.getSuperclassIterator(eq(classLoader), anyString())).thenReturn(iterator); + boolean compareResult = matcher.compareClassName(classLoader, "info.novatec.test.Fail"); + assertThat(compareResult, is(false)); + compareResult = matcher.compareClassName(classLoader, "info.novatec.Fail"); + assertThat(compareResult, is(false)); + compareResult = matcher.compareClassName(classLoader, "info.novatec.*"); + assertThat(compareResult, is(false)); + compareResult = matcher.compareClassName(classLoader, ""); + assertThat(compareResult, is(false)); + + verifyZeroInteractions(classPoolAnalyzer); + verify(inheritanceAnalyzer, times(4)).getSuperclassIterator(eq(classLoader), anyString()); + verifyNoMoreInteractions(inheritanceAnalyzer); + } + + public void testMethod() { + } + + public void testMethod(String msg) { + } + + @Test + public void getMatchingMethods() throws NotFoundException { + ClassLoader classLoader = this.getClass().getClassLoader(); + CtMethod[] ctMethods = new CtMethod[2]; + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(this.getClass().getName()); + ctMethods[0] = ctClass.getDeclaredMethod("testMethod", null); + ctMethods[1] = ctClass.getDeclaredMethod("testMethod", new CtClass[] { classPool.get("java.lang.String") }); + + // stub the getMethodsForClassName method + when(classPoolAnalyzer.getMethodsForClassName(classLoader, "info.novatec.test.Test")).thenReturn(ctMethods); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + matcher.checkParameters(ctMethodList); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, hasSize(1)); + assertThat(ctMethodList.get(0).getParameterTypes().length, is(equalTo(1))); + assertThat(ctMethodList.get(0).getParameterTypes()[0].getName(), is(equalTo("java.lang.String"))); + + verify(classPoolAnalyzer, times(1)).getMethodsForClassName(classLoader, "info.novatec.test.Test"); + verifyNoMoreInteractions(classPoolAnalyzer); + } + + @Test + public void getMatchingMethodsIgnoreSignature() throws NotFoundException { + // ignore the signature, now the result should contain two methods + unregisteredSensorConfig.setIgnoreSignature(true); + + ClassLoader classLoader = this.getClass().getClassLoader(); + CtMethod[] ctMethods = new CtMethod[2]; + ClassPool classPool = ClassPool.getDefault(); + CtClass ctClass = classPool.get(this.getClass().getName()); + ctMethods[0] = ctClass.getDeclaredMethod("testMethod", null); + ctMethods[1] = ctClass.getDeclaredMethod("testMethod", new CtClass[] { classPool.get("java.lang.String") }); + + // stub the getMethodsForClassName method + when(classPoolAnalyzer.getMethodsForClassName(classLoader, "info.novatec.test.Test")).thenReturn(ctMethods); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, hasSize(2)); + + verify(classPoolAnalyzer, times(1)).getMethodsForClassName(classLoader, "info.novatec.test.Test"); + verifyNoMoreInteractions(classPoolAnalyzer); + } + + @Test + public void getMatchingMethodsNoMethods() throws NotFoundException { + ClassLoader classLoader = this.getClass().getClassLoader(); + CtMethod[] ctMethods = new CtMethod[0]; + + // stub the getMethodsForClassName method + when(classPoolAnalyzer.getMethodsForClassName(classLoader, "info.novatec.test.Test")).thenReturn(ctMethods); + + // execute the test call + List ctMethodList = matcher.getMatchingMethods(classLoader, "info.novatec.test.Test"); + assertThat(ctMethodList, is(notNullValue())); + assertThat(ctMethodList, is(empty())); + + verify(classPoolAnalyzer, times(1)).getMethodsForClassName(classLoader, "info.novatec.test.Test"); + verifyNoMoreInteractions(classPoolAnalyzer); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/classes/AbstractSubTest.java b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/AbstractSubTest.java new file mode 100644 index 000000000..9a50962d3 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/AbstractSubTest.java @@ -0,0 +1,6 @@ +package info.novatec.inspectit.agent.analyzer.classes; + +@SuppressWarnings("PMD") +public abstract class AbstractSubTest extends AbstractTest implements ITestTwo { + +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/classes/AbstractTest.java b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/AbstractTest.java new file mode 100644 index 000000000..48ea12f3f --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/AbstractTest.java @@ -0,0 +1,8 @@ +package info.novatec.inspectit.agent.analyzer.classes; + +public abstract class AbstractTest implements ISubTest { + + public void empty() { + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/classes/EmptyClass.java b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/EmptyClass.java new file mode 100644 index 000000000..abd767da7 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/EmptyClass.java @@ -0,0 +1,6 @@ +package info.novatec.inspectit.agent.analyzer.classes; + +@SuppressWarnings("PMD") +public class EmptyClass { + +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/classes/ExceptionTestClass.java b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/ExceptionTestClass.java new file mode 100644 index 000000000..34b705ee7 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/ExceptionTestClass.java @@ -0,0 +1,59 @@ +package info.novatec.inspectit.agent.analyzer.classes; + +/** + * DO NOT MODIFY THIS CLASS UNLESS YOU KNOW WHAT YOU'RE DOING. + * + * @author Eduard Tudenhoefner + * + */ +@SuppressWarnings("PMD") +public class ExceptionTestClass { + + public void throwsAndHandlesException() { + try { + throw new MyTestException(); + } catch (MyTestException e) { + } + } + + public void createsExceptionObject() { + new MyTestException(); + } + + public void callsMethodWithException() { + try { + throwsAnException(); + } catch (MyTestException exception) { + } + } + + public void throwsAnException() throws MyTestException { + throw new MyTestException(); + } + + public static void callsStaticMethodWithException() { + try { + ExceptionTestClass.staticThrowsAnException(); + } catch (MyTestException exception) { + } + } + + public static void staticThrowsAnException() throws MyTestException { + throw new MyTestException(); + } + + public void constructorThrowsAnException() { + try { + new ExceptionalTestClass("bla"); + } catch (MyTestException e) { + } + } + + public void callsMethodWithExceptionAndTryCatchFinally() { + try { + throwsAnException(); + } catch (MyTestException e) { + } finally { + } + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/classes/ExceptionalTestClass.java b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/ExceptionalTestClass.java new file mode 100644 index 000000000..af357186e --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/ExceptionalTestClass.java @@ -0,0 +1,18 @@ +package info.novatec.inspectit.agent.analyzer.classes; + +/** + * DO NOT MODIFY THIS CLASS UNLESS YOU KNOW WHAT YOU'RE DOING. + * + * @author Eduard Tudenhoefner + * + */ +@SuppressWarnings("PMD") +public class ExceptionalTestClass { + public ExceptionalTestClass() { + } + + public ExceptionalTestClass(String message) throws MyTestException { + throw new MyTestException(message); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/classes/ISubTest.java b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/ISubTest.java new file mode 100644 index 000000000..e585cac46 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/ISubTest.java @@ -0,0 +1,5 @@ +package info.novatec.inspectit.agent.analyzer.classes; + +public interface ISubTest extends ITest { + +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/classes/ITest.java b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/ITest.java new file mode 100644 index 000000000..03117a432 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/ITest.java @@ -0,0 +1,9 @@ +package info.novatec.inspectit.agent.analyzer.classes; + +public interface ITest { + + void voidNullParameter(); + + String stringNullParameter(); + +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/classes/ITestTwo.java b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/ITestTwo.java new file mode 100644 index 000000000..107628751 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/ITestTwo.java @@ -0,0 +1,5 @@ +package info.novatec.inspectit.agent.analyzer.classes; + +public interface ITestTwo { + +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/classes/MyTestError.java b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/MyTestError.java new file mode 100644 index 000000000..ee08998cd --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/MyTestError.java @@ -0,0 +1,23 @@ +package info.novatec.inspectit.agent.analyzer.classes; + +@SuppressWarnings("PMD") +public class MyTestError extends Error { + + /** + * Generated UID. + */ + private static final long serialVersionUID = 5830128876783922090L; + + MyTestError() { + super(); + } + + MyTestError(String message) { + super(message); + } + + MyTestError(Throwable cause) { + super(cause); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/classes/MyTestException.java b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/MyTestException.java new file mode 100644 index 000000000..386ff3b30 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/MyTestException.java @@ -0,0 +1,23 @@ +package info.novatec.inspectit.agent.analyzer.classes; + +@SuppressWarnings("PMD") +public class MyTestException extends Exception { + + /** + * Generated UID. + */ + private static final long serialVersionUID = 5733336673451228795L; + + public MyTestException() { + super(); + } + + public MyTestException(String message) { + super(message); + } + + public MyTestException(Throwable cause) { + super(cause); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/classes/TestClass.java b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/TestClass.java new file mode 100644 index 000000000..8a86710e1 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/TestClass.java @@ -0,0 +1,85 @@ +package info.novatec.inspectit.agent.analyzer.classes; + +/** + * DO NOT MODIFY UNLESS YOU ARE SURE WHAT YOU ARE DOING! + * + * @author Patrice Bouillet + * + */ +@SuppressWarnings("PMD") +public class TestClass extends AbstractSubTest { + + public TestClass() { + } + + public TestClass(String text) { + } + + public TestClass(boolean delegate) { + this("delegate"); + } + + public void voidNullParameter() { + } + + public String stringNullParameter() { + return "stringNullParameter"; + } + + public int intNullParameter() { + return 3; + } + + public double doubleNullParameter() { + return 5.3D; + } + + public float floatNullParameter() { + return Float.MAX_VALUE; + } + + public byte byteNullParameter() { + return 127; + } + + public short shortNullParameter() { + return 16345; + } + + public boolean booleanNullParameter() { + return false; + } + + public char charNullParameter() { + return '\u1234'; + } + + public static void voidNullParameterStatic() { + } + + public static String stringNullParameterStatic() { + return "stringNullParameterStatic"; + } + + public void voidOneParameter(String parameterOne) { + } + + public String stringOneParameter(String parameterOne) { + return "stringOneParameter"; + } + + public void voidTwoParameters(String parameterOne, Object parameterTwo) { + } + + public void mixedTwoParameters(int parameterOne, boolean parameterTwo) { + } + + public int[] intArrayNullParameter() { + return new int[] { 1, 2, 3 }; + } + + public String[] stringArrayNullParameter() { + return new String[] { "test123", "bla" }; + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/classes/TestClassLoader.java b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/TestClassLoader.java new file mode 100644 index 000000000..07d81a631 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/classes/TestClassLoader.java @@ -0,0 +1,14 @@ +package info.novatec.inspectit.agent.analyzer.classes; + +/** + * Test {@link ClassLoader} that always returns it's own class in {@link #loadClass(String)}. + * + * @author Ivan Senic + * + */ +public class TestClassLoader extends ClassLoader { + + public java.lang.Class loadClass(String name) throws ClassNotFoundException { + return this.getClass(); + }; +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/impl/ByteCodeAnalyzerTest.java b/Agent/test/info/novatec/inspectit/agent/analyzer/impl/ByteCodeAnalyzerTest.java new file mode 100644 index 000000000..4730396ce --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/impl/ByteCodeAnalyzerTest.java @@ -0,0 +1,408 @@ +package info.novatec.inspectit.agent.analyzer.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.analyzer.IClassPoolAnalyzer; +import info.novatec.inspectit.agent.analyzer.IInheritanceAnalyzer; +import info.novatec.inspectit.agent.analyzer.IMatcher; +import info.novatec.inspectit.agent.analyzer.classes.MyTestException; +import info.novatec.inspectit.agent.analyzer.classes.TestClass; +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.config.impl.MethodSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.PropertyAccessor; +import info.novatec.inspectit.agent.config.impl.PropertyAccessor.PropertyPathStart; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.config.impl.UnregisteredSensorConfig; +import info.novatec.inspectit.agent.hooking.IHookInstrumenter; +import info.novatec.inspectit.agent.hooking.impl.HookException; +import info.novatec.inspectit.agent.sensor.exception.ExceptionSensor; +import info.novatec.inspectit.agent.sensor.exception.IExceptionSensor; +import info.novatec.inspectit.agent.sensor.method.IMethodSensor; +import info.novatec.inspectit.communication.data.ParameterContentType; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class ByteCodeAnalyzerTest extends AbstractLogSupport { + + @Mock + private IConfigurationStorage configurationStorage; + + @Mock + private IHookInstrumenter hookInstrumenter; + + @Mock + private IClassPoolAnalyzer classPoolAnalyzer; + + @Mock + private IInheritanceAnalyzer inheritanceAnalyzer; + + private ByteCodeAnalyzer byteCodeAnalyzer; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + byteCodeAnalyzer = new ByteCodeAnalyzer(configurationStorage, hookInstrumenter, classPoolAnalyzer); + when(configurationStorage.getClassLoaderDelegationMatcher()).thenReturn(mock(IMatcher.class)); + } + + private byte[] getByteCode(String className) throws NotFoundException, IOException, CannotCompileException { + CtClass ctClass = ClassPool.getDefault().get(className); + return ctClass.toBytecode(); + } + + @Test + public void nothingToDo() throws NotFoundException, IOException, CannotCompileException { + String className = TestClass.class.getName(); + ClassLoader classLoader = TestClass.class.getClassLoader(); + byte[] byteCode = getByteCode(className); + + ClassPool classPool = ClassPool.getDefault(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + + byte[] instrumentedByteCode = byteCodeAnalyzer.analyzeAndInstrument(byteCode, className, classLoader); + + // as no instrumentation happened, we get a null object + assertThat(instrumentedByteCode, is(nullValue())); + } + + @Test + public void simpleClassAndMethod() throws NotFoundException, IOException, CannotCompileException { + String className = TestClass.class.getName(); + String methodName = "voidNullParameter"; + ClassLoader classLoader = TestClass.class.getClassLoader(); + byte[] byteCode = getByteCode(className); + + ClassPool classPool = ClassPool.getDefault(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + + List unregisteredSensorConfigs = new ArrayList(); + UnregisteredSensorConfig unregisteredSensorConfig = mock(UnregisteredSensorConfig.class); + when(unregisteredSensorConfig.getTargetClassName()).thenReturn(className); + when(unregisteredSensorConfig.getTargetMethodName()).thenReturn(methodName); + MethodSensorTypeConfig methodSensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(methodSensorTypeConfig.getClassName()).thenReturn(""); + IMethodSensor methodSensor = mock(IMethodSensor.class); + when(methodSensorTypeConfig.getSensorType()).thenReturn(methodSensor); + when(unregisteredSensorConfig.getSensorTypeConfig()).thenReturn(methodSensorTypeConfig); + IMatcher matcher = mock(IMatcher.class); + List ctMethods = new ArrayList(); + ctMethods.add(ClassPool.getDefault().getMethod(className, methodName)); + when(matcher.compareClassName(classLoader, className)).thenReturn(true); + when(matcher.getMatchingMethods(classLoader, className)).thenReturn(ctMethods); + when(unregisteredSensorConfig.getMatcher()).thenReturn(matcher); + unregisteredSensorConfigs.add(unregisteredSensorConfig); + + when(configurationStorage.getUnregisteredSensorConfigs()).thenReturn(unregisteredSensorConfigs); + + byte[] instrumentedByteCode = byteCodeAnalyzer.analyzeAndInstrument(byteCode, className, classLoader); + + assertThat(instrumentedByteCode, is(notNullValue())); + // nothing was really instrumented, thus the byte code has to be the + // same + assertThat(instrumentedByteCode, is(equalTo(byteCode))); + } + + @Test + public void removeReturnValueCapturingForVoidReturnMethods() throws NotFoundException, IOException, CannotCompileException, HookException { + String className = TestClass.class.getName(); + String methodName = "voidNullParameter"; + ClassLoader classLoader = TestClass.class.getClassLoader(); + byte[] byteCode = getByteCode(className); + + ClassPool classPool = ClassPool.getDefault(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + + List unregisteredSensorConfigs = new ArrayList(); + UnregisteredSensorConfig unregisteredSensorConfig = mock(UnregisteredSensorConfig.class); + when(unregisteredSensorConfig.getTargetClassName()).thenReturn(className); + when(unregisteredSensorConfig.getTargetMethodName()).thenReturn(methodName); + MethodSensorTypeConfig methodSensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(methodSensorTypeConfig.getClassName()).thenReturn(""); + + IMethodSensor methodSensor = mock(IMethodSensor.class); + when(methodSensorTypeConfig.getSensorType()).thenReturn(methodSensor); + when(unregisteredSensorConfig.getSensorTypeConfig()).thenReturn(methodSensorTypeConfig); + + IMatcher matcher = mock(IMatcher.class); + List ctMethods = new ArrayList(); + ctMethods.add(ClassPool.getDefault().getMethod(className, methodName)); + when(matcher.compareClassName(classLoader, className)).thenReturn(true); + when(matcher.getMatchingMethods(classLoader, className)).thenReturn(ctMethods); + when(unregisteredSensorConfig.getMatcher()).thenReturn(matcher); + + List propertyAccessors = new ArrayList(); + PropertyPathStart path = new PropertyPathStart(); + path.setContentType(ParameterContentType.RETURN); + path.setName("returnValue"); + propertyAccessors.add(path); + when(unregisteredSensorConfig.getPropertyAccessorList()).thenReturn(propertyAccessors); + when(unregisteredSensorConfig.isPropertyAccess()).thenReturn(true); + + unregisteredSensorConfigs.add(unregisteredSensorConfig); + when(configurationStorage.getUnregisteredSensorConfigs()).thenReturn(unregisteredSensorConfigs); + + byteCodeAnalyzer.analyzeAndInstrument(byteCode, className, classLoader); + + ArgumentCaptor capturedRegisteredSensorConfig = ArgumentCaptor.forClass(RegisteredSensorConfig.class); + Mockito.verify(hookInstrumenter).addMethodHook(Mockito.any(CtMethod.class), capturedRegisteredSensorConfig.capture()); + assertThat(capturedRegisteredSensorConfig.getValue().getPropertyAccessorList(), is(empty())); + assertThat(capturedRegisteredSensorConfig.getValue().isPropertyAccess(), is(false)); + } + + @Test + public void removeReturnValueCapturingForVoidReturnMethodsButKeepOthers() throws NotFoundException, IOException, CannotCompileException, HookException { + String className = TestClass.class.getName(); + String methodName = "voidNullParameter"; + ClassLoader classLoader = TestClass.class.getClassLoader(); + byte[] byteCode = getByteCode(className); + + ClassPool classPool = ClassPool.getDefault(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + + List unregisteredSensorConfigs = new ArrayList(); + UnregisteredSensorConfig unregisteredSensorConfig = mock(UnregisteredSensorConfig.class); + when(unregisteredSensorConfig.getTargetClassName()).thenReturn(className); + when(unregisteredSensorConfig.getTargetMethodName()).thenReturn(methodName); + MethodSensorTypeConfig methodSensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(methodSensorTypeConfig.getClassName()).thenReturn(""); + + IMethodSensor methodSensor = mock(IMethodSensor.class); + when(methodSensorTypeConfig.getSensorType()).thenReturn(methodSensor); + when(unregisteredSensorConfig.getSensorTypeConfig()).thenReturn(methodSensorTypeConfig); + + IMatcher matcher = mock(IMatcher.class); + List ctMethods = new ArrayList(); + ctMethods.add(ClassPool.getDefault().getMethod(className, methodName)); + when(matcher.compareClassName(classLoader, className)).thenReturn(true); + when(matcher.getMatchingMethods(classLoader, className)).thenReturn(ctMethods); + when(unregisteredSensorConfig.getMatcher()).thenReturn(matcher); + + List propertyAccessors = new ArrayList(); + + PropertyPathStart path = new PropertyPathStart(); + path.setContentType(ParameterContentType.RETURN); + path.setName("returnValue"); + propertyAccessors.add(path); + + path = new PropertyPathStart(); + path.setContentType(ParameterContentType.PARAM); + path.setName("returnValue"); + propertyAccessors.add(path); + when(unregisteredSensorConfig.getPropertyAccessorList()).thenReturn(propertyAccessors); + when(unregisteredSensorConfig.isPropertyAccess()).thenReturn(true); + + unregisteredSensorConfigs.add(unregisteredSensorConfig); + when(configurationStorage.getUnregisteredSensorConfigs()).thenReturn(unregisteredSensorConfigs); + + byteCodeAnalyzer.analyzeAndInstrument(byteCode, className, classLoader); + + ArgumentCaptor capturedRegisteredSensorConfig = ArgumentCaptor.forClass(RegisteredSensorConfig.class); + Mockito.verify(hookInstrumenter).addMethodHook(Mockito.any(CtMethod.class), capturedRegisteredSensorConfig.capture()); + assertThat(capturedRegisteredSensorConfig.getValue().getPropertyAccessorList(), hasSize(1)); + assertThat(capturedRegisteredSensorConfig.getValue().isPropertyAccess(), is(true)); + assertThat(capturedRegisteredSensorConfig.getValue().getPropertyAccessorList(), contains(path)); + } + + @Test + public void noRemovalOfReturnValueCapturingForNonVoidReturnMethods() throws NotFoundException, IOException, CannotCompileException, HookException { + String className = TestClass.class.getName(); + String methodName = "stringNullParameter"; + ClassLoader classLoader = TestClass.class.getClassLoader(); + byte[] byteCode = getByteCode(className); + + ClassPool classPool = ClassPool.getDefault(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + + List unregisteredSensorConfigs = new ArrayList(); + UnregisteredSensorConfig unregisteredSensorConfig = mock(UnregisteredSensorConfig.class); + when(unregisteredSensorConfig.getTargetClassName()).thenReturn(className); + when(unregisteredSensorConfig.getTargetMethodName()).thenReturn(methodName); + MethodSensorTypeConfig methodSensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(methodSensorTypeConfig.getClassName()).thenReturn(""); + + IMethodSensor methodSensor = mock(IMethodSensor.class); + when(methodSensorTypeConfig.getSensorType()).thenReturn(methodSensor); + when(unregisteredSensorConfig.getSensorTypeConfig()).thenReturn(methodSensorTypeConfig); + + IMatcher matcher = mock(IMatcher.class); + List ctMethods = new ArrayList(); + ctMethods.add(ClassPool.getDefault().getMethod(className, methodName)); + when(matcher.compareClassName(classLoader, className)).thenReturn(true); + when(matcher.getMatchingMethods(classLoader, className)).thenReturn(ctMethods); + when(unregisteredSensorConfig.getMatcher()).thenReturn(matcher); + + List propertyAccessors = new ArrayList(); + + PropertyPathStart path = new PropertyPathStart(); + path.setContentType(ParameterContentType.RETURN); + path.setName("returnValue"); + propertyAccessors.add(path); + + path = new PropertyPathStart(); + path.setContentType(ParameterContentType.PARAM); + path.setName("returnValue"); + propertyAccessors.add(path); + when(unregisteredSensorConfig.getPropertyAccessorList()).thenReturn(propertyAccessors); + when(unregisteredSensorConfig.isPropertyAccess()).thenReturn(true); + + unregisteredSensorConfigs.add(unregisteredSensorConfig); + when(configurationStorage.getUnregisteredSensorConfigs()).thenReturn(unregisteredSensorConfigs); + + byteCodeAnalyzer.analyzeAndInstrument(byteCode, className, classLoader); + + ArgumentCaptor capturedRegisteredSensorConfig = ArgumentCaptor.forClass(RegisteredSensorConfig.class); + Mockito.verify(hookInstrumenter).addMethodHook(Mockito.any(CtMethod.class), capturedRegisteredSensorConfig.capture()); + assertThat(capturedRegisteredSensorConfig.getValue().getPropertyAccessorList(), hasSize(2)); + assertThat(capturedRegisteredSensorConfig.getValue().isPropertyAccess(), is(true)); + } + + @Test + public void methodWithOneParameter() throws NotFoundException, IOException, CannotCompileException { + String className = TestClass.class.getName(); + String methodName = "voidOneParameter"; + ClassLoader classLoader = TestClass.class.getClassLoader(); + byte[] byteCode = getByteCode(className); + + ClassPool classPool = ClassPool.getDefault(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + + List unregisteredSensorConfigs = new ArrayList(); + UnregisteredSensorConfig unregisteredSensorConfig = mock(UnregisteredSensorConfig.class); + when(unregisteredSensorConfig.getTargetClassName()).thenReturn(className); + when(unregisteredSensorConfig.getTargetMethodName()).thenReturn(methodName); + MethodSensorTypeConfig methodSensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(methodSensorTypeConfig.getClassName()).thenReturn(""); + IMethodSensor methodSensor = mock(IMethodSensor.class); + when(methodSensorTypeConfig.getSensorType()).thenReturn(methodSensor); + when(unregisteredSensorConfig.getSensorTypeConfig()).thenReturn(methodSensorTypeConfig); + IMatcher matcher = mock(IMatcher.class); + List ctMethods = new ArrayList(); + ctMethods.add(ClassPool.getDefault().getMethod(className, methodName)); + when(matcher.compareClassName(classLoader, className)).thenReturn(true); + when(matcher.getMatchingMethods(classLoader, className)).thenReturn(ctMethods); + when(unregisteredSensorConfig.getMatcher()).thenReturn(matcher); + unregisteredSensorConfigs.add(unregisteredSensorConfig); + + when(configurationStorage.getUnregisteredSensorConfigs()).thenReturn(unregisteredSensorConfigs); + + byte[] instrumentedByteCode = byteCodeAnalyzer.analyzeAndInstrument(byteCode, className, classLoader); + + assertThat(instrumentedByteCode, is(notNullValue())); + // nothing was really instrumented, thus the byte code has to be the + // same + assertThat(instrumentedByteCode, is(equalTo(byteCode))); + } + + @Test + public void exceptionSensorNotActivated() throws NotFoundException, IOException, CannotCompileException { + String className = MyTestException.class.getName(); + ClassLoader classLoader = MyTestException.class.getClassLoader(); + byte[] byteCode = getByteCode(className); + + when(configurationStorage.isExceptionSensorActivated()).thenReturn(false); + ClassPool classPool = ClassPool.getDefault(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + + // actual class was not a subclass of Throwable, so nothing to + // instrument + byte[] instrumentedByteCode = byteCodeAnalyzer.analyzeAndInstrument(byteCode, className, classLoader); + assertThat(instrumentedByteCode, is(nullValue())); + } + + @Test + public void exceptionSensorActivated() throws NotFoundException, IOException, CannotCompileException { + String className = MyTestException.class.getName(); + ClassLoader classLoader = MyTestException.class.getClassLoader(); + byte[] byteCode = getByteCode(className); + + ClassPool classPool = ClassPool.getDefault(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + when(inheritanceAnalyzer.subclassOf(className, "java.lang.Throwable", classPool)).thenReturn(true); + IExceptionSensor exceptionSensor = mock(ExceptionSensor.class); + + MethodSensorTypeConfig sensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(sensorTypeConfig.getName()).thenReturn(exceptionSensor.getClass().getName()); + when(sensorTypeConfig.getClassName()).thenReturn(exceptionSensor.getClass().getName()); + when(sensorTypeConfig.getSensorType()).thenReturn(exceptionSensor); + List exceptionSensorTypes = new ArrayList(); + exceptionSensorTypes.add(sensorTypeConfig); + when(configurationStorage.getMethodSensorTypes()).thenReturn(exceptionSensorTypes); + when(configurationStorage.getExceptionSensorTypes()).thenReturn(exceptionSensorTypes); + IMatcher superclassMatcher = mock(IMatcher.class); + when(superclassMatcher.compareClassName(classLoader, className)).thenReturn(true); + + Map settings = new HashMap(); + settings.put("superclass", "true"); + + List exceptionSensorConfigs = new ArrayList(); + UnregisteredSensorConfig config = mock(UnregisteredSensorConfig.class); + when(config.isConstructor()).thenReturn(true); + when(config.isInterface()).thenReturn(false); + when(config.isSuperclass()).thenReturn(true); + when(config.isVirtual()).thenReturn(false); + when(config.isIgnoreSignature()).thenReturn(true); + when(config.getSensorTypeConfig()).thenReturn(sensorTypeConfig); + when(config.getSettings()).thenReturn(settings); + ThrowableMatcher matcher = new ThrowableMatcher(inheritanceAnalyzer, classPoolAnalyzer, config, superclassMatcher); + when(config.getMatcher()).thenReturn(matcher); + exceptionSensorConfigs.add(config); + + when(configurationStorage.getUnregisteredSensorConfigs()).thenReturn(exceptionSensorConfigs); + + // exception sensor was activated, so the current Throwable class was + // instrumented + byte[] instrumentedByteCode = byteCodeAnalyzer.analyzeAndInstrument(byteCode, className, classLoader); + assertThat(instrumentedByteCode, is(notNullValue())); + } + + @Test + public void classLoaderInstumentation() throws NotFoundException, IOException, CannotCompileException, HookException { + ClassLoader classLoader = this.getClass().getClassLoader(); + Class classLoaderClass = classLoader.getClass(); + String className = classLoaderClass.getName(); + byte[] byteCode = getByteCode(className); + + ClassPool classPool = ClassPool.getDefault(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + when(configurationStorage.getUnregisteredSensorConfigs()).thenReturn(Collections. emptyList()); + List methodList = new ArrayList(); + methodList.add(classPool.getMethod(className, "loadClass")); + + IMatcher matcher = mock(IMatcher.class); + when(matcher.compareClassName(classLoader, className)).thenReturn(true); + when(matcher.getMatchingMethods(classLoader, className)).thenReturn(methodList); + when(configurationStorage.getClassLoaderDelegationMatcher()).thenReturn(matcher); + + byteCodeAnalyzer.analyzeAndInstrument(byteCode, className, classLoader); + + verify(hookInstrumenter, times(1)).addClassLoaderDelegationHook(methodList.get(0)); + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/impl/ClassPoolAnalyzerTest.java b/Agent/test/info/novatec/inspectit/agent/analyzer/impl/ClassPoolAnalyzerTest.java new file mode 100644 index 000000000..240b09842 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/impl/ClassPoolAnalyzerTest.java @@ -0,0 +1,125 @@ +package info.novatec.inspectit.agent.analyzer.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.analyzer.classes.TestClass; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; + +import javassist.ClassPool; +import javassist.CtMethod; + +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class ClassPoolAnalyzerTest extends AbstractLogSupport { + + private ClassPoolAnalyzer classPoolAnalyzer; + + @BeforeMethod + public void init() { + classPoolAnalyzer = new ClassPoolAnalyzer(); + classPoolAnalyzer.log = LoggerFactory.getLogger(ClassPoolAnalyzer.class); + } + + @Test + public void getMethodsForClassName() { + CtMethod[] ctMethods = classPoolAnalyzer.getMethodsForClassName(TestClass.class.getClassLoader(), TestClass.class.getName()); + assertThat(ctMethods, is(notNullValue())); + assertThat(ctMethods.length, is(equalTo(TestClass.class.getDeclaredMethods().length))); + } + + @Test + public void getMethodsForClassNameNullClassLoader() { + CtMethod[] ctMethods = classPoolAnalyzer.getMethodsForClassName(null, TestClass.class.getName()); + assertThat(ctMethods, is(notNullValue())); + assertThat(ctMethods.length, is(equalTo(TestClass.class.getDeclaredMethods().length))); + } + + @Test + public void getMethodsForClassNameNullClassName() { + CtMethod[] ctMethods = classPoolAnalyzer.getMethodsForClassName(TestClass.class.getClassLoader(), null); + assertThat(ctMethods, is(notNullValue())); + assertThat(ctMethods.length, is(equalTo(0))); + } + + @Test + public void getMethodsForClassNameEmptyClassName() { + CtMethod[] ctMethods = classPoolAnalyzer.getMethodsForClassName(TestClass.class.getClassLoader(), ""); + assertThat(ctMethods, is(notNullValue())); + assertThat(ctMethods.length, is(equalTo(0))); + } + + @Test + public void equalClassPool() { + ClassPool classPool = classPoolAnalyzer.addClassLoader(TestClass.class.getClassLoader()); + assertThat(classPool, is(notNullValue())); + ClassPool otherClassPool = classPoolAnalyzer.getClassPool(TestClass.class.getClassLoader()); + assertThat(otherClassPool, is(notNullValue())); + assertThat(classPool, is(equalTo(otherClassPool))); + } + + @Test + public void extClassLoaderParent() { + ClassPool classPool = classPoolAnalyzer.getClassPool(TestClass.class.getClassLoader()); + assertThat(classPool, is(notNullValue())); + ClassPool appClassPool = classPoolAnalyzer.getClassPool(TestClass.class.getClassLoader().getParent()); + assertThat(appClassPool, is(notNullValue())); + assertThat(appClassPool.getClassLoader().toString(), containsString("AppClassLoader")); + } + + private class TestClassLoader extends ClassLoader { + public TestClassLoader(ClassLoader parent) { + super(parent); + } + } + + @Test + public void classLoaderHierarchy() throws Exception { + TestClassLoader testClassLoader = new TestClassLoader(TestClass.class.getClassLoader()); + TestClassLoader subTestClassLoader = new TestClassLoader(testClassLoader); + TestClassLoader subSubTestClassLoader = new TestClassLoader(subTestClassLoader); + + classPoolAnalyzer.addClassLoader(subSubTestClassLoader); + + ClassPool classPool = classPoolAnalyzer.getClassPool(subSubTestClassLoader); + assertThat(getClassLoader(classPool), is(equalTo((ClassLoader) subSubTestClassLoader))); + ClassPool parentClassPool = getParentClassPool(classPool); + assertThat(getClassLoader(parentClassPool), is(equalTo((ClassLoader) subTestClassLoader))); + ClassPool parentParentClassPool = getParentClassPool(parentClassPool); + assertThat(getClassLoader(parentParentClassPool), is(equalTo((ClassLoader) testClassLoader))); + } + + private ClassPool getParentClassPool(ClassPool classPool) throws Exception { + // only possible through reflection :( + Field field = ClassPool.class.getDeclaredField("parent"); + field.setAccessible(true); + return (ClassPool) field.get(classPool); + } + + @SuppressWarnings("unchecked") + private ClassLoader getClassLoader(ClassPool classPool) throws Exception { + // more reflection, yay! + Field field = ClassPool.class.getDeclaredField("source"); + field.setAccessible(true); + Object classPoolTail = field.get(classPool); + field = classPoolTail.getClass().getDeclaredField("pathList"); + field.setAccessible(true); + Object classPathList = field.get(classPoolTail); + field = classPathList.getClass().getDeclaredField("path"); + field.setAccessible(true); + Object classPath = field.get(classPathList); + field = classPath.getClass().getDeclaredField("clref"); + field.setAccessible(true); + WeakReference weakReference = (WeakReference) field.get(classPath); + return weakReference.get(); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/analyzer/impl/InheritanceAnalyzerTest.java b/Agent/test/info/novatec/inspectit/agent/analyzer/impl/InheritanceAnalyzerTest.java new file mode 100644 index 000000000..5bb911517 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/analyzer/impl/InheritanceAnalyzerTest.java @@ -0,0 +1,207 @@ +package info.novatec.inspectit.agent.analyzer.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.MockInit; +import info.novatec.inspectit.agent.analyzer.IClassPoolAnalyzer; +import info.novatec.inspectit.agent.analyzer.classes.AbstractSubTest; +import info.novatec.inspectit.agent.analyzer.classes.AbstractTest; +import info.novatec.inspectit.agent.analyzer.classes.ISubTest; +import info.novatec.inspectit.agent.analyzer.classes.ITest; +import info.novatec.inspectit.agent.analyzer.classes.ITestTwo; +import info.novatec.inspectit.agent.analyzer.classes.MyTestError; +import info.novatec.inspectit.agent.analyzer.classes.MyTestException; +import info.novatec.inspectit.agent.analyzer.classes.TestClass; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; + +import org.mockito.Mock; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class InheritanceAnalyzerTest extends MockInit { + + @Mock + private IClassPoolAnalyzer classPoolAnalyzer; + + private InheritanceAnalyzer inheritanceAnalyzer; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + inheritanceAnalyzer = new InheritanceAnalyzer(classPoolAnalyzer); + inheritanceAnalyzer.log = LoggerFactory.getLogger(InheritanceAnalyzer.class); + } + + @Test + public void getSuperclassIterator() throws NotFoundException { + // set up everything + ClassLoader classLoader = TestClass.class.getClassLoader(); + String className = TestClass.class.getName(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(ClassPool.getDefault()); + + // main test + Iterator iterator = inheritanceAnalyzer.getSuperclassIterator(classLoader, className); + assertThat(iterator, is(notNullValue())); + assertThat(iterator.hasNext(), is(true)); + CtClass superclass = iterator.next(); + assertThat(superclass.getName(), is(equalTo(AbstractSubTest.class.getName()))); + + assertThat(iterator.hasNext(), is(true)); + superclass = iterator.next(); + assertThat(superclass.getName(), is(equalTo(AbstractTest.class.getName()))); + + assertThat(iterator.hasNext(), is(true)); + superclass = iterator.next(); + assertThat(superclass.getName(), is(equalTo(Object.class.getName()))); + + assertThat(iterator.hasNext(), is(false)); + + verify(classPoolAnalyzer, times(1)).getClassPool(classLoader); + verifyNoMoreInteractions(classPoolAnalyzer); + } + + @Test + public void getInterfaceIterator() throws NotFoundException { + // set up everything + ClassLoader classLoader = TestClass.class.getClassLoader(); + String className = TestClass.class.getName(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(ClassPool.getDefault()); + + List interfaceList = new ArrayList(3); + interfaceList.add(ClassPool.getDefault().get(ITestTwo.class.getName())); + interfaceList.add(ClassPool.getDefault().get(ISubTest.class.getName())); + interfaceList.add(ClassPool.getDefault().get(ITest.class.getName())); + + // main test + Iterator iterator = inheritanceAnalyzer.getInterfaceIterator(classLoader, className); + assertThat(iterator, is(notNullValue())); + + assertThat(iterator.hasNext(), is(true)); + CtClass interfaceCtClass = iterator.next(); + assertThat(interfaceList, hasItem(interfaceCtClass)); + interfaceList.remove(interfaceCtClass); + + assertThat(iterator.hasNext(), is(true)); + interfaceCtClass = iterator.next(); + assertThat(interfaceList, hasItem(interfaceCtClass)); + interfaceList.remove(interfaceCtClass); + + assertThat(iterator.hasNext(), is(true)); + interfaceCtClass = iterator.next(); + assertThat(interfaceList, hasItem(interfaceCtClass)); + interfaceList.remove(interfaceCtClass); + + assertThat(iterator.hasNext(), is(false)); + + verify(classPoolAnalyzer, times(1)).getClassPool(classLoader); + verifyNoMoreInteractions(classPoolAnalyzer); + } + + @Test(expectedExceptions = { NotFoundException.class }) + public void superclassNotFound() throws NotFoundException { + // set up everything + ClassLoader classLoader = this.getClass().getClassLoader(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(ClassPool.getDefault()); + + inheritanceAnalyzer.getSuperclassIterator(classLoader, "xxx"); + } + + @Test(expectedExceptions = { NotFoundException.class }) + public void interfaceNotFound() throws NotFoundException { + // set up everything + ClassLoader classLoader = this.getClass().getClassLoader(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(ClassPool.getDefault()); + + inheritanceAnalyzer.getInterfaceIterator(classLoader, "xxx"); + } + + @Test(expectedExceptions = { NotFoundException.class }) + public void superclassNullClassName() throws NotFoundException { + // set up everything + ClassLoader classLoader = this.getClass().getClassLoader(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(ClassPool.getDefault()); + + inheritanceAnalyzer.getSuperclassIterator(classLoader, null); + } + + @Test(expectedExceptions = { NotFoundException.class }) + public void interfaceNullClassName() throws NotFoundException { + // set up everything + ClassLoader classLoader = this.getClass().getClassLoader(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(ClassPool.getDefault()); + + inheritanceAnalyzer.getInterfaceIterator(classLoader, null); + } + + @Test + public void superclassEmptyResult() throws NotFoundException { + // set up everything + ClassLoader classLoader = Object.class.getClassLoader(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(ClassPool.getDefault()); + + Iterator iterator = inheritanceAnalyzer.getSuperclassIterator(classLoader, Object.class.getName()); + assertThat(iterator, is(notNullValue())); + assertThat(iterator.hasNext(), is(false)); + + verify(classPoolAnalyzer, times(1)).getClassPool(classLoader); + verifyNoMoreInteractions(classPoolAnalyzer); + } + + @Test + public void interfaceEmptyResult() throws NotFoundException { + // set up everything + ClassLoader classLoader = Object.class.getClassLoader(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(ClassPool.getDefault()); + + Iterator iterator = inheritanceAnalyzer.getInterfaceIterator(classLoader, Object.class.getName()); + assertThat(iterator, is(notNullValue())); + assertThat(iterator.hasNext(), is(false)); + + verify(classPoolAnalyzer, times(1)).getClassPool(classLoader); + verifyNoMoreInteractions(classPoolAnalyzer); + } + + @Test + public void subclassOfThrowable() { + // set up everything + ClassLoader classLoader = MyTestException.class.getClassLoader(); + String className = MyTestException.class.getName(); + ClassPool classPool = ClassPool.getDefault(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + + // main test + boolean subclassOfThrowable = inheritanceAnalyzer.subclassOf(className, "java.lang.Throwable", classPool); + assertThat(subclassOfThrowable, is(true)); + + boolean subclassOfException = inheritanceAnalyzer.subclassOf(className, "java.lang.Exception", classPool); + assertThat(subclassOfException, is(true)); + } + + @Test + public void subclassOfError() { + // set up everything + ClassLoader classLoader = MyTestError.class.getClassLoader(); + String className = MyTestError.class.getName(); + ClassPool classPool = ClassPool.getDefault(); + when(classPoolAnalyzer.getClassPool(classLoader)).thenReturn(classPool); + + boolean subclassOfError = inheritanceAnalyzer.subclassOf(className, "java.lang.Error", classPool); + assertThat(subclassOfError, is(true)); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/buffer/impl/SimpleBufferStrategyTest.java b/Agent/test/info/novatec/inspectit/agent/buffer/impl/SimpleBufferStrategyTest.java new file mode 100644 index 000000000..541d0ed04 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/buffer/impl/SimpleBufferStrategyTest.java @@ -0,0 +1,67 @@ +package info.novatec.inspectit.agent.buffer.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import info.novatec.inspectit.communication.MethodSensorData; + +import java.util.Collections; +import java.util.List; +import java.util.NoSuchElementException; + +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class SimpleBufferStrategyTest { + + private SimpleBufferStrategy bufferStrategy; + + @BeforeMethod + public void initTestClass() { + bufferStrategy = new SimpleBufferStrategy(); + bufferStrategy.log = LoggerFactory.getLogger(SimpleBufferStrategy.class); + } + + @Test + public void addAndRetrieve() { + bufferStrategy.addMeasurements(Collections. emptyList()); + + assertThat(bufferStrategy.hasNext(), is(true)); + List list = bufferStrategy.next(); + assertThat(list, is(notNullValue())); + assertThat(list, is(equalTo(Collections. emptyList()))); + + assertThat(bufferStrategy.hasNext(), is(false)); + } + + @Test + public void emptyBuffer() { + assertThat(bufferStrategy.hasNext(), is(false)); + } + + @Test(expectedExceptions = { NoSuchElementException.class }) + public void noSuchElementException() { + bufferStrategy.next(); + } + + @Test(expectedExceptions = { IllegalArgumentException.class }) + public void addNullMeasurement() { + bufferStrategy.addMeasurements(null); + } + + @Test(expectedExceptions = { NoSuchElementException.class }) + public void exceptionAfterDoubleRetrieve() { + bufferStrategy.addMeasurements(Collections. emptyList()); + bufferStrategy.next(); + bufferStrategy.next(); + } + + @Test + public void callInit() { + bufferStrategy.init(Collections. emptyMap()); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/buffer/impl/SizeBufferStrategyTest.java b/Agent/test/info/novatec/inspectit/agent/buffer/impl/SizeBufferStrategyTest.java new file mode 100644 index 000000000..fff3c0eaa --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/buffer/impl/SizeBufferStrategyTest.java @@ -0,0 +1,97 @@ +package info.novatec.inspectit.agent.buffer.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import info.novatec.inspectit.communication.MethodSensorData; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class SizeBufferStrategyTest { + + private SizeBufferStrategy bufferStrategy; + + @BeforeMethod + public void initTestClass() { + bufferStrategy = new SizeBufferStrategy(); + bufferStrategy.log = LoggerFactory.getLogger(SizeBufferStrategy.class); + } + + @Test + public void addAndRetrieve() { + bufferStrategy.addMeasurements(Collections. emptyList()); + + assertThat(bufferStrategy.hasNext(), is(true)); + List list = bufferStrategy.next(); + assertThat(list, is(notNullValue())); + assertThat(list, is(equalTo(Collections. emptyList()))); + + assertThat(bufferStrategy.hasNext(), is(false)); + } + + @Test + public void emptyBuffer() { + assertThat(bufferStrategy.hasNext(), is(false)); + } + + @Test(expectedExceptions = { NoSuchElementException.class }) + public void noSuchElementException() { + bufferStrategy.next(); + } + + @Test(expectedExceptions = { IllegalArgumentException.class }) + public void addNullMeasurement() { + bufferStrategy.addMeasurements(null); + } + + @Test(expectedExceptions = { NoSuchElementException.class }) + public void exceptionAfterDoubleRetrieve() { + bufferStrategy.addMeasurements(Collections. emptyList()); + bufferStrategy.next(); + bufferStrategy.next(); + } + + @Test + public void callInit() { + Map settings = new HashMap(); + settings.put("size", "3"); + bufferStrategy.init(Collections. emptyMap()); + } + + @Test + public void addElementFullStack() { + Map settings = new HashMap(); + settings.put("size", "3"); + bufferStrategy.init(settings); + + List listOne = new ArrayList(0); + List listTwo = new ArrayList(0); + List listThree = new ArrayList(0); + List listFour = new ArrayList(0); + List listFive = new ArrayList(0); + + bufferStrategy.addMeasurements(listOne); + bufferStrategy.addMeasurements(listTwo); + bufferStrategy.addMeasurements(listThree); + bufferStrategy.addMeasurements(listFour); + bufferStrategy.addMeasurements(listFive); + + assertThat(bufferStrategy.next(), is(equalTo(listFive))); + assertThat(bufferStrategy.next(), is(equalTo(listFour))); + assertThat(bufferStrategy.next(), is(equalTo(listThree))); + + assertThat(bufferStrategy.hasNext(), is(false)); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/config/impl/ConfigurationFilesTest.java b/Agent/test/info/novatec/inspectit/agent/config/impl/ConfigurationFilesTest.java new file mode 100644 index 000000000..2c091f319 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/config/impl/ConfigurationFilesTest.java @@ -0,0 +1,78 @@ +package info.novatec.inspectit.agent.config.impl; + +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.config.ParserException; + +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class ConfigurationFilesTest extends AbstractLogSupport { + + private static final String PATH_TO_CONFIG_FILES = "config"; + + private FileConfigurationReader fileConfigurationReader; + + @Mock + private IConfigurationStorage configurationStorage; + + @BeforeMethod + public void init() { + MockitoAnnotations.initMocks(this); + fileConfigurationReader = new FileConfigurationReader(configurationStorage); + fileConfigurationReader.log = LoggerFactory.getLogger(FileConfigurationReader.class); + } + + @Test(dataProvider = "Paths-To-Config-Files-Provider") + public void processConfigurationFile(String path) throws FileNotFoundException, ParserException { + File configFile = new File(path); + InputStream is = new FileInputStream(configFile); + InputStreamReader reader = new InputStreamReader(is); + fileConfigurationReader.parse(reader, path); + } + + @DataProvider(name = "Paths-To-Config-Files-Provider") + public Object[][] getConfigurationFiles() throws IOException { + File dir = new File(PATH_TO_CONFIG_FILES); + List paths = new ArrayList(); + processDir(dir, paths); + Object[][] result = new Object[paths.size()][1]; + for (int i = 0; i < paths.size(); i++) { + result[i][0] = paths.get(i); + } + return result; + } + + private void processDir(File dir, List paths) throws IOException { + if (dir.isDirectory()) { + File[] files = dir.listFiles(new FileFilter() { + public boolean accept(File pathname) { + return pathname.isDirectory() || pathname.getAbsolutePath().endsWith(".cfg"); + } + }); + for (int i = 0; i < files.length; i++) { + if (files[i].isDirectory()) { + processDir(files[i], paths); + } else { + paths.add(files[i].getPath()); + } + } + } else { + throw new IOException("Given path to the configuration files is not valid."); + } + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/config/impl/ConfigurationStorageTest.java b/Agent/test/info/novatec/inspectit/agent/config/impl/ConfigurationStorageTest.java new file mode 100644 index 000000000..cb9998939 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/config/impl/ConfigurationStorageTest.java @@ -0,0 +1,679 @@ +package info.novatec.inspectit.agent.config.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Mockito.verifyZeroInteractions; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.analyzer.IClassPoolAnalyzer; +import info.novatec.inspectit.agent.analyzer.IInheritanceAnalyzer; +import info.novatec.inspectit.agent.analyzer.IMatchPattern; +import info.novatec.inspectit.agent.analyzer.impl.DirectMatcher; +import info.novatec.inspectit.agent.analyzer.impl.IndirectMatcher; +import info.novatec.inspectit.agent.analyzer.impl.InterfaceMatcher; +import info.novatec.inspectit.agent.analyzer.impl.SuperclassMatcher; +import info.novatec.inspectit.agent.analyzer.impl.ThrowableMatcher; +import info.novatec.inspectit.agent.config.PriorityEnum; +import info.novatec.inspectit.agent.config.StorageException; +import info.novatec.inspectit.agent.config.impl.PropertyAccessor.PropertyPath; +import info.novatec.inspectit.agent.config.impl.PropertyAccessor.PropertyPathStart; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javassist.Modifier; + +import org.mockito.Mock; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class ConfigurationStorageTest extends AbstractLogSupport { + + @Mock + private IClassPoolAnalyzer classPoolAnalyzer; + + @Mock + private IInheritanceAnalyzer inheritanceAnalyzer; + + private ConfigurationStorage configurationStorage; + + /** + * This method will be executed before every method is executed in here. This ensures that some + * tests don't modify the contents of the configuration storage. + */ + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() throws StorageException { + configurationStorage = new ConfigurationStorage(classPoolAnalyzer, inheritanceAnalyzer); + configurationStorage.log = LoggerFactory.getLogger(ConfigurationStorage.class); + + // name and repository + configurationStorage.setAgentName("UnitTestAgent"); + configurationStorage.setRepository("localhost", 1099); + + // method sensor types + Map settings = new HashMap(1); + settings.put("mode", "optimized"); + configurationStorage.addMethodSensorType("timer", "info.novatec.inspectit.agent.sensor.method.timer.TimerSensor", PriorityEnum.MAX, settings); + configurationStorage.addMethodSensorType("isequence", "info.novatec.inspectit.agent.sensor.method.invocationsequence.InvocationSequenceSensor", PriorityEnum.INVOC, null); + + // platform sensor types + configurationStorage.addPlatformSensorType("info.novatec.inspectit.agent.sensor.platform.ClassLoadingInformation", null); + configurationStorage.addPlatformSensorType("info.novatec.inspectit.agent.sensor.platform.CompilationInformation", null); + configurationStorage.addPlatformSensorType("info.novatec.inspectit.agent.sensor.platform.RuntimeInformation", null); + + // exception sensor + configurationStorage.addExceptionSensorType("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor", null); + + // exception sensor parameters + settings = new HashMap(); + settings.put("superclass", "true"); + configurationStorage.addExceptionSensorTypeParameter("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor", "java.lang.Throwable", false, settings); + + settings = new HashMap(); + settings.put("interface", "true"); + configurationStorage.addExceptionSensorTypeParameter("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor", "info.novatec.inspectit.agent.analyzer.test.classes.IException", false, + settings); + + configurationStorage.addExceptionSensorTypeParameter("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor", "info.novatec.inspectit.agent.analyzer.test.classes.My*Exception", true, + Collections. emptyMap()); + configurationStorage.addExceptionSensorTypeParameter("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor", "info.novatec.inspectit.agent.analyzer.test.classes.MyException", false, + Collections. emptyMap()); + + // sending strategies + Map sendingSettings = new HashMap(1); + sendingSettings.put("time", "5000"); + configurationStorage.addSendingStrategy("info.novatec.inspectit.agent.sending.impl.TimeStrategy", sendingSettings); + + sendingSettings = new HashMap(1); + sendingSettings.put("size", "10"); + configurationStorage.addSendingStrategy("info.novatec.inspectit.agent.sending.impl.ListSizeStrategy", sendingSettings); + + // buffer strategy + configurationStorage.setBufferStrategy("info.novatec.inspectit.agent.buffer.impl.SimpleBufferStrategy", null); + + // sensor definitions + configurationStorage.addSensor("timer", "*", "*", null, true, null); + + configurationStorage.addSensor("isequence", "info.novatec.inspectitsamples.calculator.Calculator", "actionPerformed", null, true, null); + + List parameterList = new ArrayList(); + parameterList.add("java.lang.String"); + configurationStorage.addSensor("timer", "info.novatec.inspectitsamples.calculator.Calculator", "actionPerformed", parameterList, false, null); + + settings = new HashMap(); + settings.put("interface", "true"); + configurationStorage.addSensor("timer", "info.novatec.IService", "*Service", null, true, settings); + + settings = new HashMap(); + settings.put("superclass", "true"); + configurationStorage.addSensor("isequence", "info.novatec.inspectitsamples.calculator.Calculator", "actionPerformed", null, true, settings); + + Map fieldSettings = new HashMap(); + List list = new ArrayList(); + list.add("LastOutput;jlbOutput.text"); + fieldSettings.put("field", list); + configurationStorage.addSensor("timer", "*", "*", null, true, fieldSettings); + + fieldSettings = new HashMap(); + list = new ArrayList(); + list.add("0;Source;msg"); + fieldSettings.put("property", list); + configurationStorage.addSensor("timer", "*", "*", null, true, fieldSettings); + + settings = new HashMap(); + settings.put("annotation", "javax.ejb.StatelessBean"); + configurationStorage.addSensor("isequence", "info.novatec.inspectitsamples.calculator.Calculator", "actionPerformed", null, false, settings); + + settings = new HashMap(); + settings.put("modifiers", "pub,prot"); + configurationStorage.addSensor("timer", "*", "*", null, true, settings); + + configurationStorage.addIgnoreClassesPattern("info.novatec.*"); + } + + @Test() + public void agentNameCheck() { + assertThat(configurationStorage.getAgentName(), is(equalTo("UnitTestAgent"))); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void setNullAgentName() throws StorageException { + configurationStorage.setAgentName(null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void setEmptyAgentName() throws StorageException { + configurationStorage.setAgentName(""); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test + public void repositoryCheck() { + assertThat(configurationStorage.getRepositoryConfig().getHost(), is(equalTo("localhost"))); + assertThat(configurationStorage.getRepositoryConfig().getPort(), is(equalTo(1099))); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void setNullRepositoryHost() throws StorageException { + configurationStorage.setRepository(null, 1099); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void setEmptyRepositoryHost() throws StorageException { + configurationStorage.setRepository("", 1099); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test + public void resetRepositoryNotAllowed() throws StorageException { + configurationStorage.setRepository("localhost1", 1200); + + assertThat(configurationStorage.getRepositoryConfig().getHost(), is(equalTo("localhost"))); + assertThat(configurationStorage.getRepositoryConfig().getPort(), is(equalTo(1099))); + } + + @Test + public void resetAgentnameNotAllowed() throws StorageException { + configurationStorage.setAgentName("agent1"); + + assertThat(configurationStorage.getAgentName(), is(equalTo("UnitTestAgent"))); + } + + @Test + public void methodSensorTypesCheck() { + List configs = configurationStorage.getMethodSensorTypes(); + assertThat(configs, is(notNullValue())); + assertThat(configs, hasSize(3)); + + // first + MethodSensorTypeConfig config = configs.get(0); + assertThat(config.getClassName(), is(equalTo("info.novatec.inspectit.agent.sensor.method.timer.TimerSensor"))); + assertThat(config.getName(), is(equalTo("timer"))); + assertThat(config.getParameters(), is(notNullValue())); + Map settings = config.getParameters(); + assertThat(settings.size(), is(1)); + assertThat(settings, hasKey("mode")); + assertThat(settings, hasEntry("mode", (Object) "optimized")); + assertThat(config.getPriority(), is(equalTo(PriorityEnum.MAX))); + assertThat(config.getSensorType(), is(nullValue())); + + // second + config = configs.get(1); + assertThat(config.getClassName(), is(equalTo("info.novatec.inspectit.agent.sensor.method.invocationsequence.InvocationSequenceSensor"))); + assertThat(config.getName(), is(equalTo("isequence"))); + assertThat(config.getParameters(), is(notNullValue())); + assertThat(config.getParameters().size(), is(0)); + assertThat(config.getPriority(), is(equalTo(PriorityEnum.INVOC))); + assertThat(config.getSensorType(), is(nullValue())); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void addNullMethodSensorTypeName() throws StorageException { + configurationStorage.addMethodSensorType(null, "xxx", PriorityEnum.NORMAL, null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void addEmptyMethodSensorTypeName() throws StorageException { + configurationStorage.addMethodSensorType("", "xxx", PriorityEnum.NORMAL, null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void addNullMethodSensorTypeClass() throws StorageException { + configurationStorage.addMethodSensorType("xxx", null, PriorityEnum.NORMAL, null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void addEmptyMethodSensorTypeClass() throws StorageException { + configurationStorage.addMethodSensorType("xxx", "", PriorityEnum.NORMAL, null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void addNullMethodSensorTypePriority() throws StorageException { + configurationStorage.addMethodSensorType("xxx", "xxx", null, null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test + public void platformSensorTypeCheck() { + List configs = configurationStorage.getPlatformSensorTypes(); + assertThat(configs, is(notNullValue())); + assertThat(configs, hasSize(3)); + + // first + PlatformSensorTypeConfig config = configs.get(0); + assertThat(config.getClassName(), is(equalTo("info.novatec.inspectit.agent.sensor.platform.ClassLoadingInformation"))); + assertThat(config.getParameters(), is(notNullValue())); + assertThat(config.getParameters().size(), is(0)); + assertThat(config.getSensorType(), is(nullValue())); + + // second + config = configs.get(1); + assertThat(config.getClassName(), is(equalTo("info.novatec.inspectit.agent.sensor.platform.CompilationInformation"))); + assertThat(config.getParameters(), is(notNullValue())); + assertThat(config.getParameters().size(), is(0)); + assertThat(config.getSensorType(), is(nullValue())); + + // third + config = configs.get(2); + assertThat(config.getClassName(), is(equalTo("info.novatec.inspectit.agent.sensor.platform.RuntimeInformation"))); + assertThat(config.getParameters(), is(notNullValue())); + assertThat(config.getParameters().size(), is(0)); + assertThat(config.getSensorType(), is(nullValue())); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void addNullPlatformSensorTypeClass() throws StorageException { + configurationStorage.addPlatformSensorType(null, null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void addEmptyPlatformSensorTypeClass() throws StorageException { + configurationStorage.addPlatformSensorType("", null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test + public void exceptionSensorCheck() { + List configs = configurationStorage.getExceptionSensorTypes(); + assertThat(configs, is(notNullValue())); + assertThat(configs, hasSize(1)); + + MethodSensorTypeConfig config = configs.get(0); + assertThat(config.getClassName(), is(equalTo("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"))); + assertThat(config.getParameters(), is(notNullValue())); + assertThat(config.getParameters().size(), is(0)); + assertThat(config.getSensorType(), is(nullValue())); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void addNullExceptionSensor() throws StorageException { + configurationStorage.addExceptionSensorType(null, null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + + } + + @Test + public void exceptionSensorParameterCheck() { + List configs = configurationStorage.getUnregisteredSensorConfigs(); + assertThat(configs, is(notNullValue())); + assertThat(configs, hasSize(13)); + + // first + UnregisteredSensorConfig config = configs.get(0); + assertThat(config.getTargetClassName(), is(equalTo("java.lang.Throwable"))); + assertThat(config.getTargetMethodName(), is(equalTo(""))); + assertThat(config.getTargetPackageName(), is(nullValue())); + assertThat(config.isConstructor(), is(true)); + assertThat(config.isIgnoreSignature(), is(true)); + assertThat(config.isInterface(), is(false)); + assertThat(config.isVirtual(), is(false)); + assertThat(config.isSuperclass(), is(true)); + assertThat(config.getMatcher(), is(instanceOf(ThrowableMatcher.class))); + assertThat(config.getParameterTypes(), is(notNullValue())); + assertThat(config.getParameterTypes(), is(empty())); + assertThat(config.getSettings(), is(notNullValue())); + assertThat(config.getSettings().size(), is(1)); + + // second + config = configs.get(1); + assertThat(config.getTargetClassName(), is(equalTo("info.novatec.inspectit.agent.analyzer.test.classes.IException"))); + assertThat(config.getTargetMethodName(), is(equalTo(""))); + assertThat(config.getTargetPackageName(), is(nullValue())); + assertThat(config.isConstructor(), is(true)); + assertThat(config.isIgnoreSignature(), is(true)); + assertThat(config.isInterface(), is(true)); + assertThat(config.isVirtual(), is(false)); + assertThat(config.isSuperclass(), is(false)); + assertThat(config.getMatcher(), is(instanceOf(ThrowableMatcher.class))); + assertThat(config.getParameterTypes(), is(notNullValue())); + assertThat(config.getParameterTypes(), is(empty())); + assertThat(config.getSettings(), is(notNullValue())); + assertThat(config.getSettings().size(), is(1)); + + // third + config = configs.get(2); + assertThat(config.getTargetClassName(), is(equalTo("info.novatec.inspectit.agent.analyzer.test.classes.My*Exception"))); + assertThat(config.getTargetMethodName(), is(equalTo(""))); + assertThat(config.getTargetPackageName(), is(nullValue())); + assertThat(config.isConstructor(), is(true)); + assertThat(config.isIgnoreSignature(), is(true)); + assertThat(config.isInterface(), is(false)); + assertThat(config.isVirtual(), is(true)); + assertThat(config.isSuperclass(), is(false)); + assertThat(config.getMatcher(), is(instanceOf(ThrowableMatcher.class))); + assertThat(config.getParameterTypes(), is(notNullValue())); + assertThat(config.getParameterTypes(), is(empty())); + assertThat(config.getSettings(), is(notNullValue())); + assertThat(config.getSettings().size(), is(0)); + + // fourth + config = configs.get(3); + assertThat(config.getTargetClassName(), is(equalTo("info.novatec.inspectit.agent.analyzer.test.classes.MyException"))); + assertThat(config.getTargetMethodName(), is(equalTo(""))); + assertThat(config.getTargetPackageName(), is(nullValue())); + assertThat(config.isConstructor(), is(true)); + assertThat(config.isIgnoreSignature(), is(true)); + assertThat(config.isInterface(), is(false)); + assertThat(config.isVirtual(), is(false)); + assertThat(config.isSuperclass(), is(false)); + assertThat(config.getMatcher(), is(instanceOf(ThrowableMatcher.class))); + assertThat(config.getParameterTypes(), is(notNullValue())); + assertThat(config.getParameterTypes(), is(empty())); + assertThat(config.getSettings(), is(notNullValue())); + assertThat(config.getSettings().size(), is(0)); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + + } + + @Test(expectedExceptions = { StorageException.class }) + public void addEmptyExceptionSensor() throws StorageException { + configurationStorage.addExceptionSensorType("", null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + + } + + @Test + public void sendingStrategiesCheck() { + List strategies = configurationStorage.getSendingStrategyConfigs(); + assertThat(strategies, is(notNullValue())); + assertThat(strategies, hasSize(2)); + + // first + StrategyConfig config = strategies.get(0); + assertThat(config.getClazzName(), is(equalTo("info.novatec.inspectit.agent.sending.impl.TimeStrategy"))); + assertThat(config.getSettings(), is(notNullValue())); + Map settings = config.getSettings(); + assertThat(settings.size(), is(1)); + assertThat(settings, hasKey("time")); + assertThat(settings, hasEntry("time", "5000")); + + // second + config = strategies.get(1); + assertThat(config.getClazzName(), is(equalTo("info.novatec.inspectit.agent.sending.impl.ListSizeStrategy"))); + assertThat(config.getSettings(), is(notNullValue())); + settings = config.getSettings(); + assertThat(settings.size(), is(1)); + assertThat(settings, hasKey("size")); + assertThat(settings, hasEntry("size", "10")); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void addNullSendingStrategy() throws StorageException { + configurationStorage.addSendingStrategy(null, null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void addEmptySendingStrategy() throws StorageException { + configurationStorage.addSendingStrategy("", null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test + public void bufferStrategyCheck() { + StrategyConfig config = configurationStorage.getBufferStrategyConfig(); + assertThat(config, is(notNullValue())); + + assertThat(config.getClazzName(), is(equalTo("info.novatec.inspectit.agent.buffer.impl.SimpleBufferStrategy"))); + assertThat(config.getSettings(), is(notNullValue())); + assertThat(config.getSettings().size(), is(0)); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void setNullBufferStrategy() throws StorageException { + configurationStorage.setBufferStrategy(null, null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void setEmptyBufferStrategy() throws StorageException { + configurationStorage.setBufferStrategy("", null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test + public void sensorCheck() { + List configs = configurationStorage.getUnregisteredSensorConfigs(); + assertThat(configs, is(notNullValue())); + assertThat(configs, hasSize(13)); + + // the first 4 configs are the ones from the exception sensor + // first + UnregisteredSensorConfig config = configs.get(4); + assertThat(config.getSensorTypeConfig().getName(), is(equalTo("timer"))); + assertThat(config.getTargetPackageName(), is(nullValue())); + assertThat(config.getTargetClassName(), is(equalTo("*"))); + assertThat(config.getTargetMethodName(), is(equalTo("*"))); + assertThat(config.getParameterTypes(), is(notNullValue())); + assertThat(config.getParameterTypes(), is(empty())); + assertThat(config.getSettings(), is(notNullValue())); + assertThat(config.getSettings().size(), is(0)); + assertThat(config.getPropertyAccessorList(), is(notNullValue())); + assertThat(config.getPropertyAccessorList(), is(empty())); + assertThat(config.getMatcher(), is(instanceOf(IndirectMatcher.class))); + + // second + config = configs.get(5); + assertThat(config.getSensorTypeConfig().getName(), is(equalTo("isequence"))); + assertThat(config.getTargetPackageName(), is(nullValue())); + assertThat(config.getTargetClassName(), is(equalTo("info.novatec.inspectitsamples.calculator.Calculator"))); + assertThat(config.getTargetMethodName(), is(equalTo("actionPerformed"))); + assertThat(config.getParameterTypes(), is(notNullValue())); + assertThat(config.getParameterTypes(), is(empty())); + assertThat(config.getSettings(), is(notNullValue())); + assertThat(config.getSettings().size(), is(0)); + assertThat(config.getPropertyAccessorList(), is(notNullValue())); + assertThat(config.getPropertyAccessorList(), is(empty())); + assertThat(config.getMatcher(), is(instanceOf(IndirectMatcher.class))); + + // third + config = configs.get(6); + assertThat(config.getSensorTypeConfig().getName(), is(equalTo("timer"))); + assertThat(config.getTargetPackageName(), is(nullValue())); + assertThat(config.getTargetClassName(), is(equalTo("info.novatec.inspectitsamples.calculator.Calculator"))); + assertThat(config.getTargetMethodName(), is(equalTo("actionPerformed"))); + assertThat(config.getParameterTypes(), is(notNullValue())); + assertThat(config.getParameterTypes(), hasSize(1)); + assertThat(config.getParameterTypes(), hasItem("java.lang.String")); + assertThat(config.getSettings(), is(notNullValue())); + assertThat(config.getSettings().size(), is(0)); + assertThat(config.getPropertyAccessorList(), is(notNullValue())); + assertThat(config.getPropertyAccessorList(), is(empty())); + assertThat(config.getMatcher(), is(instanceOf(DirectMatcher.class))); + + // fourth + config = configs.get(7); + assertThat(config.getSensorTypeConfig().getName(), is(equalTo("timer"))); + assertThat(config.getTargetPackageName(), is(nullValue())); + assertThat(config.getTargetClassName(), is(equalTo("info.novatec.IService"))); + assertThat(config.getTargetMethodName(), is(equalTo("*Service"))); + assertThat(config.getParameterTypes(), is(notNullValue())); + assertThat(config.getParameterTypes(), is(empty())); + assertThat(config.getSettings(), is(notNullValue())); + assertThat(config.getSettings().size(), is(1)); + assertThat(config.getSettings(), hasKey("interface")); + assertThat(config.getSettings(), hasEntry("interface", (Object) "true")); + assertThat(config.getPropertyAccessorList(), is(notNullValue())); + assertThat(config.getPropertyAccessorList(), is(empty())); + assertThat(config.getMatcher(), is(instanceOf(InterfaceMatcher.class))); + + // fifth + config = configs.get(8); + assertThat(config.getSensorTypeConfig().getName(), is(equalTo("isequence"))); + assertThat(config.getTargetPackageName(), is(nullValue())); + assertThat(config.getTargetClassName(), is(equalTo("info.novatec.inspectitsamples.calculator.Calculator"))); + assertThat(config.getTargetMethodName(), is(equalTo("actionPerformed"))); + assertThat(config.getParameterTypes(), is(notNullValue())); + assertThat(config.getParameterTypes(), is(empty())); + assertThat(config.getSettings(), is(notNullValue())); + assertThat(config.getSettings().size(), is(1)); + assertThat(config.getSettings(), hasKey("superclass")); + assertThat(config.getSettings(), hasEntry("superclass", (Object) "true")); + assertThat(config.getPropertyAccessorList(), is(notNullValue())); + assertThat(config.getPropertyAccessorList(), is(empty())); + assertThat(config.getMatcher(), is(instanceOf(SuperclassMatcher.class))); + + // sixth + config = configs.get(9); + assertThat(config.getPropertyAccessorList(), hasSize(1)); + assertThat(config.getPropertyAccessorList().get(0), is(instanceOf(PropertyPathStart.class))); + PropertyPathStart start = (PropertyPathStart) config.getPropertyAccessorList().get(0); + assertThat(start.getName(), is(equalTo("LastOutput"))); + assertThat(start.getSignaturePosition(), is(-1)); + assertThat(start.getPathToContinue(), is(instanceOf(PropertyPath.class))); + assertThat(start.getPathToContinue().getName(), is(equalTo("jlbOutput"))); + assertThat(start.getPathToContinue().getPathToContinue(), is(instanceOf(PropertyPath.class))); + assertThat(start.getPathToContinue().getPathToContinue().getName(), is(equalTo("text"))); + assertThat(start.getPathToContinue().getPathToContinue().getPathToContinue(), is(nullValue())); + + // seventh + config = configs.get(10); + assertThat(config.getPropertyAccessorList(), hasSize(1)); + assertThat(config.getPropertyAccessorList().get(0), is(instanceOf(PropertyPathStart.class))); + start = (PropertyPathStart) config.getPropertyAccessorList().get(0); + assertThat(start.getName(), is(equalTo("Source"))); + assertThat(start.getSignaturePosition(), is(0)); + assertThat(start.getPathToContinue(), is(instanceOf(PropertyPath.class))); + assertThat(start.getPathToContinue().getName(), is(equalTo("msg"))); + assertThat(start.getPathToContinue().getPathToContinue(), is(nullValue())); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void addNullSensorTypeName() throws StorageException { + configurationStorage.addSensor(null, "xxx", "xxx", null, false, null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void addEmptySensorTypeName() throws StorageException { + configurationStorage.addSensor("", "xxx", "xxx", null, false, null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void addNullSensorTargetClassName() throws StorageException { + configurationStorage.addSensor("xxx", null, "xxx", null, false, null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void addEmptySensorTargetClassName() throws StorageException { + configurationStorage.addSensor("xxx", "", "xxx", null, false, null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void addNullSensorTargetMethodName() throws StorageException { + configurationStorage.addSensor("xxx", "xxx", null, null, false, null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void addEmptySensorTargetMethodName() throws StorageException { + configurationStorage.addSensor("xxx", "xxx", "", null, false, null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test(expectedExceptions = { StorageException.class }) + public void addSensorInvalidSensorTypeName() throws StorageException { + configurationStorage.addSensor("xxx", "xxx", "xxx", null, false, null); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test + public void annotationCheck() { + List configs = configurationStorage.getUnregisteredSensorConfigs(); + assertThat(configs, is(notNullValue())); + + UnregisteredSensorConfig annotationConfig = configs.get(11); + assertThat(annotationConfig.getAnnotationClassName(), is(notNullValue())); + assertThat(annotationConfig.getAnnotationClassName(), is(equalTo("javax.ejb.StatelessBean"))); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test + public void modifiersCheck() { + List configs = configurationStorage.getUnregisteredSensorConfigs(); + assertThat(configs, is(notNullValue())); + + // 11 is index of config with modifiers + UnregisteredSensorConfig configWithModifiers = configs.get(12); + assertThat(configWithModifiers.getSettings(), hasKey("modifiers")); + assertThat(configWithModifiers.getModifiers(), is(not(0))); + assertThat(Modifier.isPublic(configWithModifiers.getModifiers()), is(true)); + assertThat(Modifier.isProtected(configWithModifiers.getModifiers()), is(true)); + + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } + + @Test + public void ignoreClassesCheck() { + List ignorePatterns = configurationStorage.getIgnoreClassesPatterns(); + assertThat(ignorePatterns, is(notNullValue())); + assertThat(ignorePatterns, is(not(empty()))); + verifyZeroInteractions(classPoolAnalyzer, inheritanceAnalyzer); + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/config/impl/FileConfigurationReaderTest.java b/Agent/test/info/novatec/inspectit/agent/config/impl/FileConfigurationReaderTest.java new file mode 100644 index 000000000..d0611a02b --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/config/impl/FileConfigurationReaderTest.java @@ -0,0 +1,516 @@ +package info.novatec.inspectit.agent.config.impl; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.config.ParserException; +import info.novatec.inspectit.agent.config.PriorityEnum; +import info.novatec.inspectit.agent.config.StorageException; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class FileConfigurationReaderTest extends AbstractLogSupport { + + private File file; + + private PrintWriter writer; + + @Mock + private IConfigurationStorage configurationStorage; + + private FileConfigurationReader fileConfigurationReader; + + @BeforeMethod(alwaysRun = true) + public void initConfigurationFile() throws Exception { + if (null == file) { + String tmpdir = System.getProperty("java.io.tmpdir"); + file = new File(tmpdir + "/inspectit-agent.cfg"); + } else { + file.delete(); + file.createNewFile(); + } + + System.setProperty("inspectit.config", System.getProperty("java.io.tmpdir")); + writer = new PrintWriter(new BufferedWriter(new FileWriter(file))); + } + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + fileConfigurationReader = new FileConfigurationReader(configurationStorage); + fileConfigurationReader.log = LoggerFactory.getLogger(FileConfigurationReader.class); + } + + @Test + public void loadAndVerifyRepository() throws ParserException, StorageException { + String localhost = "localhost"; + int port = 1099; + String agentName = "CalculatorTestAgent"; + + writer.println("repository " + localhost + " " + port + " " + agentName); + writer.close(); + + fileConfigurationReader.load(); + + verify(configurationStorage, times(1)).setRepository(localhost, 1099); + verify(configurationStorage, times(1)).setAgentName(agentName); + } + + @Test + public void loadAndVerifyMethodSensorType() throws ParserException, StorageException { + String name = "average-timer"; + String clazz = "info.novatec.inspectit.agent.sensor.method.averagetimer.AverageTimerSensor"; + PriorityEnum priority = PriorityEnum.HIGH; + + writer.println("method-sensor-type " + name + " " + clazz + " " + priority); + writer.close(); + + fileConfigurationReader.load(); + + verify(configurationStorage, times(1)).addMethodSensorType(name, clazz, priority, Collections. emptyMap()); + } + + @Test + public void loadAndVerifyMethodSensorTypeWithParameter() throws ParserException, StorageException { + String name = "average-timer"; + String clazz = "info.novatec.inspectit.agent.sensor.method.averagetimer.AverageTimerSensor"; + PriorityEnum priority = PriorityEnum.HIGH; + + Map settings = new HashMap(); + settings.put("stringLength", "500"); + String parameterString = "stringLength=500"; + + writer.println("method-sensor-type " + name + " " + clazz + " " + priority + " " + parameterString); + writer.close(); + + fileConfigurationReader.load(); + + verify(configurationStorage, times(1)).addMethodSensorType(name, clazz, priority, settings); + } + + @Test + public void loadAndVerifyPlatformSensorType() throws ParserException, StorageException { + String clazz = "info.novatec.inspectit.agent.sensor.platform.ClassLoadingInformation"; + + writer.println("platform-sensor-type " + clazz); + writer.close(); + + fileConfigurationReader.load(); + + verify(configurationStorage, times(1)).addPlatformSensorType(clazz, Collections. emptyMap()); + } + + @Test + public void loadAndVerifyExceptionSensorType() throws ParserException, StorageException { + String name = "exception-sensor-type"; + String clazz = "info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"; + + writer.println(name + " " + clazz); + writer.close(); + + fileConfigurationReader.load(); + + verify(configurationStorage, times(1)).addExceptionSensorType(clazz, Collections. emptyMap()); + verify(configurationStorage, times(1)).setEnhancedExceptionSensorActivated(false); + } + + @Test + public void loadAndVerifyExceptionSensorTypeAdvanced() throws ParserException, StorageException { + String name = "exception-sensor-type"; + String clazz = "info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"; + + Map settings = new HashMap(); + settings.put("mode", "enhanced"); + settings.put("stringLength", "500"); + String parameterString = "mode=enhanced stringLength=500"; + + writer.println(name + " " + clazz + " " + parameterString); + writer.close(); + + fileConfigurationReader.load(); + + verify(configurationStorage, times(1)).addExceptionSensorType(clazz, settings); + verify(configurationStorage, times(1)).setEnhancedExceptionSensorActivated(true); + } + + @Test + public void loadAndVerifyExceptionSensorNoParameter() throws ParserException, StorageException { + String clazz = "info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"; + String name = "exception-sensor"; + String targetClass = "java.lang.Throwable"; + writer.println(name + " " + targetClass); + writer.close(); + + fileConfigurationReader.load(); + + verify(configurationStorage, times(1)).addExceptionSensorTypeParameter(clazz, targetClass, false, Collections. emptyMap()); + } + + @Test + public void loadAndVerifyExceptionSensorWithParameter() throws ParserException, StorageException { + String clazz = "info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"; + String name = "exception-sensor"; + String targetClass = "java.lang.Throwable"; + + Map settings = new HashMap(); + settings.put("superclass", "true"); + String parameterString = "superclass=true"; + + writer.println(name + " " + targetClass + " " + parameterString); + writer.close(); + + fileConfigurationReader.load(); + + verify(configurationStorage, times(1)).addExceptionSensorTypeParameter(clazz, "java.lang.Throwable", false, settings); + } + + @Test + public void loadAndVerifyExceptionSensorWildcardParameter() throws ParserException, StorageException { + String clazz = "info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"; + String name = "exception-sensor"; + String targetClass = "java.lang.*"; + writer.println(name + " " + targetClass); + writer.close(); + + fileConfigurationReader.load(); + + verify(configurationStorage, times(1)).addExceptionSensorTypeParameter(clazz, targetClass, true, Collections. emptyMap()); + } + + @Test + public void loadAndVerifyExceptionSensorModeSimple() throws ParserException, StorageException { + String name = "exception-sensor-type"; + String clazz = "info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"; + String mode = "mode=simple"; + writer.println(name + " " + clazz + " " + mode); + writer.close(); + + fileConfigurationReader.load(); + + verify(configurationStorage, times(1)).setEnhancedExceptionSensorActivated(false); + verify(configurationStorage, times(1)).addExceptionSensorType(Mockito.anyString(), Mockito.anyMapOf(String.class, Object.class)); + } + + @Test + public void loadAndVerifyExceptionSensorModeEnhanced() throws ParserException, StorageException { + String name = "exception-sensor-type"; + String clazz = "info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"; + String mode = "mode=enhanced"; + writer.println(name + " " + clazz + " " + mode); + writer.close(); + + fileConfigurationReader.load(); + + verify(configurationStorage, times(1)).setEnhancedExceptionSensorActivated(true); + verify(configurationStorage, times(1)).addExceptionSensorType(Mockito.anyString(), Mockito.anyMapOf(String.class, Object.class)); + } + + @Test + public void loadAndVerifyBufferStrategy() throws ParserException, StorageException { + String clazz = "info.novatec.inspectit.agent.buffer.impl.SimpleBufferStrategy"; + + writer.println("buffer-strategy " + clazz); + writer.close(); + + fileConfigurationReader.load(); + + verify(configurationStorage, times(1)).setBufferStrategy(clazz, Collections. emptyMap()); + } + + @Test + public void loadAndVerifySendingStrategy() throws ParserException, StorageException { + String clazz = "info.novatec.inspectit.agent.sending.impl.TimeStrategy"; + String time = "time=5000"; + + writer.println("send-strategy " + clazz + " " + time); + writer.close(); + + fileConfigurationReader.load(); + + Map settings = new HashMap(); + settings.put("time", "5000"); + + verify(configurationStorage, times(1)).addSendingStrategy(clazz, settings); + } + + @Test + public void loadAndVerifyStandardSensor() throws ParserException, StorageException { + String sensorTypeName = "isequence"; + String className = "info.novatec.inspectitsamples.calculator.Calculator"; + String methodName = "actionPerformed"; + + writer.println("sensor " + sensorTypeName + " " + className + " " + methodName + "() "); + writer.close(); + + fileConfigurationReader.load(); + + verify(configurationStorage, times(1)).addSensor(sensorTypeName, className, methodName, Collections. emptyList(), false, Collections. emptyMap()); + } + + @Test + public void loadAndVerifyRegexSensor() throws ParserException, StorageException { + String sensorTypeName = "timer"; + String className = "*"; + String methodName = "*"; + + writer.println("sensor " + sensorTypeName + " " + className + " " + methodName + "() "); + writer.close(); + + fileConfigurationReader.load(); + + verify(configurationStorage, times(1)).addSensor(sensorTypeName, className, methodName, Collections. emptyList(), false, Collections. emptyMap()); + } + + @Test + public void loadAndVerifySensorIgnoreSignature() throws ParserException, StorageException { + String sensorTypeName = "isequence"; + String className = "info.novatec.inspectitsamples.calculator.Calculator"; + String methodName = "actionPerformed"; + + writer.println("sensor " + sensorTypeName + " " + className + " " + methodName + " "); + writer.close(); + + fileConfigurationReader.load(); + + verify(configurationStorage, times(1)).addSensor(sensorTypeName, className, methodName, Collections. emptyList(), true, Collections. emptyMap()); + } + + @Test + public void loadAndVerifySensorWithOneParameter() throws ParserException, StorageException { + String sensorTypeName = "isequence"; + String className = "info.novatec.inspectitsamples.calculator.Calculator"; + String methodName = "actionPerformed"; + String parameter = "java.lang.String"; + + writer.println("sensor " + sensorTypeName + " " + className + " " + methodName + "(" + parameter + ") "); + writer.close(); + + fileConfigurationReader.load(); + + List parameterList = new ArrayList(); + parameterList.add(parameter); + + verify(configurationStorage, times(1)).addSensor(sensorTypeName, className, methodName, parameterList, false, Collections. emptyMap()); + } + + @Test + public void loadAndVerifySensorWithManyParameter() throws ParserException, StorageException { + String sensorTypeName = "isequence"; + String className = "info.novatec.inspectitsamples.calculator.Calculator"; + String methodName = "actionPerformed"; + String parameterOne = "java.lang.String"; + String parameterTwo = "java.lang.Object"; + String parameterThree = "java.io.File"; + + writer.println("sensor " + sensorTypeName + " " + className + " " + methodName + "(" + parameterOne + "," + parameterTwo + "," + parameterThree + ") "); + writer.close(); + + fileConfigurationReader.load(); + + List parameterList = new ArrayList(); + parameterList.add(parameterOne); + parameterList.add(parameterTwo); + parameterList.add(parameterThree); + + verify(configurationStorage, times(1)).addSensor(sensorTypeName, className, methodName, parameterList, false, Collections. emptyMap()); + } + + @Test + public void loadAndVerifySensorWithParameterRecord() throws ParserException, StorageException { + String sensorTypeName = "isequence"; + String className = "info.novatec.inspectitsamples.calculator.Calculator"; + String methodName = "actionPerformed"; + String parameterRecord = "0;Source;text"; + + writer.println("sensor " + sensorTypeName + " " + className + " " + methodName + "() " + " p=" + parameterRecord); + writer.close(); + + fileConfigurationReader.load(); + + Map settings = new HashMap(); + List propertyList = new ArrayList(); + propertyList.add(parameterRecord); + settings.put("property", propertyList); + + verify(configurationStorage, times(1)).addSensor(sensorTypeName, className, methodName, Collections. emptyList(), false, settings); + } + + @Test + public void loadAndVerifySensorWithFieldRecord() throws ParserException, StorageException { + String sensorTypeName = "isequence"; + String className = "info.novatec.inspectitsamples.calculator.Calculator"; + String methodName = "actionPerformed"; + String fieldRecord = "LastOutput;jlbOutput.text"; + + writer.println("sensor " + sensorTypeName + " " + className + " " + methodName + "() " + " f=" + fieldRecord); + writer.close(); + + fileConfigurationReader.load(); + + Map settings = new HashMap(); + List propertyList = new ArrayList(); + propertyList.add(fieldRecord); + settings.put("field", propertyList); + + verify(configurationStorage, times(1)).addSensor(sensorTypeName, className, methodName, Collections. emptyList(), false, settings); + } + + @Test + public void loadAndVerifyInvocSensorWithMinDuration() throws ParserException, StorageException { + String sensorTypeName = "isequence"; + String className = "info.novatec.inspectitsamples.calculator.Calculator"; + String methodName = "actionPerformed"; + String minDuration = "100.0"; + + writer.println("sensor " + sensorTypeName + " " + className + " " + methodName + "() " + " minDuration=" + minDuration); + writer.close(); + + fileConfigurationReader.load(); + + Map settings = new HashMap(); + settings.put("minDuration", minDuration); + + verify(configurationStorage, times(1)).addSensor(sensorTypeName, className, methodName, Collections. emptyList(), false, settings); + } + + @Test + public void loadAndVerifyAnnotation() throws ParserException, StorageException { + String sensorTypeName = "isequence"; + String className = "info.novatec.inspectitsamples.calculator.Calculator"; + String methodName = "actionPerformed"; + String annotationClassName = "javax.ejb.StatelessBean"; + + writer.println("sensor " + sensorTypeName + " " + className + " " + methodName + "() " + " @" + annotationClassName); + writer.close(); + + fileConfigurationReader.load(); + + Map settings = new HashMap(); + settings.put("annotation", annotationClassName); + + verify(configurationStorage, times(1)).addSensor(sensorTypeName, className, methodName, Collections. emptyList(), false, settings); + } + + @Test(expectedExceptions = { ParserException.class }) + public void loadInvalidFile() throws ParserException { + System.setProperty("inspectit.config", ""); + + fileConfigurationReader.load(); + } + + @Test + public void loadAndVerifyModifiers() throws ParserException, StorageException { + String sensorTypeName = "isequence"; + String className = "info.novatec.inspectitsamples.calculator.Calculator"; + String methodName = "actionPerformed"; + String modifiers = "pub,priv"; + + writer.println("sensor " + sensorTypeName + " " + className + " " + methodName + "() " + " modifiers=" + modifiers); + writer.close(); + + fileConfigurationReader.load(); + + Map settings = new HashMap(); + settings.put("modifiers", modifiers); + + verify(configurationStorage, times(1)).addSensor(sensorTypeName, className, methodName, Collections. emptyList(), false, settings); + } + + @Test + public void loadAndVerifyExcludeClasses() throws ParserException { + String patternString = "info.novatec.*"; + + writer.println("exclude-class" + " " + patternString); + writer.close(); + + fileConfigurationReader.load(); + + verify(configurationStorage, times(1)).addIgnoreClassesPattern(patternString); + } + + @Test + public void loadIncludeWithAbsolutePath() throws ParserException, IOException { + String include = "$include"; + String tmpdir = System.getProperty("java.io.tmpdir"); + + String additionalFileName = "add-config-file.cfg"; + File additionalFile = new File(tmpdir + File.separatorChar + additionalFileName); + additionalFile.createNewFile(); + + writer.println(include + " " + additionalFile.getAbsolutePath()); + writer.close(); + + fileConfigurationReader.load(); + + additionalFile.delete(); + } + + @Test + public void loadIncludeWithRelativePath() throws ParserException, IOException { + String include = "$include"; + String tmpdir = System.getProperty("java.io.tmpdir"); + + String additionalFileName = "add-config-file.cfg"; + File additionalFile = new File(tmpdir + File.separatorChar + additionalFileName); + additionalFile.createNewFile(); + + writer.println(include + " " + additionalFileName); + writer.close(); + + fileConfigurationReader.load(); + + additionalFile.delete(); + } + + @Test + public void loadIncludesWithRelativePathAndSubDirectories() throws ParserException, IOException { + String include = "$include"; + String tmpdir = System.getProperty("java.io.tmpdir"); + new File(tmpdir + File.separatorChar + "config" + File.separatorChar + "sub").mkdirs(); + + String additionalFileNameOne = "config" + File.separatorChar + "add-config-file.cfg"; + File additionalFileOne = new File(tmpdir + File.separatorChar + additionalFileNameOne); + additionalFileOne.createNewFile(); + PrintWriter additionalFileOneWriter = new PrintWriter(new BufferedWriter(new FileWriter(additionalFileOne))); + + String additionalFileNameTwo = "sub" + File.separatorChar + "another-config-file.cfg"; + File additionalFileTwo = new File(tmpdir + File.separatorChar + "config" + File.separatorChar + additionalFileNameTwo); + additionalFileTwo.createNewFile(); + + writer.println(include + " " + additionalFileNameOne); + writer.close(); + + additionalFileOneWriter.println(include + " " + additionalFileNameTwo); + additionalFileOneWriter.close(); + + fileConfigurationReader.load(); + + additionalFileOne.delete(); + additionalFileTwo.delete(); + } + + @AfterClass(alwaysRun = true) + public void deleteConfiguration() { + if (null != file) { + file.delete(); + } + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/config/impl/PropertyAccessorTest.java b/Agent/test/info/novatec/inspectit/agent/config/impl/PropertyAccessorTest.java new file mode 100644 index 000000000..ea3b24329 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/config/impl/PropertyAccessorTest.java @@ -0,0 +1,504 @@ +package info.novatec.inspectit.agent.config.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.config.PropertyAccessException; +import info.novatec.inspectit.agent.config.impl.PropertyAccessor.PropertyPath; +import info.novatec.inspectit.agent.config.impl.PropertyAccessor.PropertyPathStart; +import info.novatec.inspectit.communication.data.ParameterContentData; +import info.novatec.inspectit.communication.data.ParameterContentType; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.mockito.Mockito; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class PropertyAccessorTest extends AbstractLogSupport { + + private PropertyAccessor propertyAccessor; + + private Object resultValueMock; + + @BeforeClass + public void initTestClass() { + propertyAccessor = new PropertyAccessor(); + propertyAccessor.log = LoggerFactory.getLogger(PropertyAccessor.class); + } + + @BeforeMethod + public void initialize() { + resultValueMock = Mockito.mock(Object.class); + } + + @Test + public void readFieldPersonObject() throws PropertyAccessException { + Person person = new Person(); + person.setName("Dirk"); + + PropertyPathStart start = new PropertyPathStart(); + start.setName("this"); + start.setContentType(ParameterContentType.FIELD); + + String result = propertyAccessor.getPropertyContent(start, person, null, resultValueMock); + assertThat(result, is("Dirk")); + Mockito.verifyZeroInteractions(resultValueMock); + } + + @Test + public void readFieldPersonName() throws PropertyAccessException { + Person person = new Person(); + person.setName("Dirk"); + + PropertyPathStart start = new PropertyPathStart(); + start.setName("this"); + start.setContentType(ParameterContentType.FIELD); + + PropertyPath path = new PropertyPath(); + path.setName("name"); + start.setPathToContinue(path); + + String result = propertyAccessor.getPropertyContent(start, person, null, resultValueMock); + assertThat(result, is("Dirk")); + Mockito.verifyZeroInteractions(resultValueMock); + } + + @Test(expectedExceptions = { PropertyAccessException.class }) + public void nullStartPath() throws PropertyAccessException { + propertyAccessor.getPropertyContent(null, null, null, null); + } + + @Test(expectedExceptions = { PropertyAccessException.class }) + public void nullNeededClassObjectForFieldAccess() throws PropertyAccessException { + PropertyPathStart start = new PropertyPathStart(); + start.setName("this"); + start.setContentType(ParameterContentType.FIELD); + + propertyAccessor.getPropertyContent(start, null, null, resultValueMock); + Mockito.verifyZeroInteractions(resultValueMock); + } + + /* + * We no longer throw exceptions in the case that the return value is null as this would + * completely remove the property accessor and this is not the desired behaviour. + */ + @Test + public void nullNeededResultObjectForResultAccess() throws PropertyAccessException { + PropertyPathStart start = new PropertyPathStart(); + start.setName("this"); + start.setContentType(ParameterContentType.RETURN); + + String result = propertyAccessor.getPropertyContent(start, null, null, null); + assertThat(result, is(equalTo("null"))); + } + + @Test(expectedExceptions = { PropertyAccessException.class }) + public void nullNeededParameterObject() throws PropertyAccessException { + PropertyPathStart start = new PropertyPathStart(); + start.setName("name"); + start.setSignaturePosition(0); + start.setContentType(ParameterContentType.PARAM); + + propertyAccessor.getPropertyContent(start, null, null, resultValueMock); + Mockito.verifyZeroInteractions(resultValueMock); + } + + @Test(expectedExceptions = { PropertyAccessException.class }) + public void parameterArrayOutOfRange() throws PropertyAccessException { + PropertyPathStart start = new PropertyPathStart(); + start.setName("name"); + start.setSignaturePosition(0); + start.setContentType(ParameterContentType.PARAM); + + propertyAccessor.getPropertyContent(start, null, new Object[0], resultValueMock); + Mockito.verifyZeroInteractions(resultValueMock); + } + + @Test(expectedExceptions = { PropertyAccessException.class }) + public void missingContentType() throws PropertyAccessException { + PropertyPathStart start = new PropertyPathStart(); + start.setName("name"); + start.setSignaturePosition(0); + + propertyAccessor.getPropertyContent(start, null, null, null); + } + + @Test(expectedExceptions = { PropertyAccessException.class }) + public void analyzePersonAccessException() throws PropertyAccessException { + Person person = new Person(); + person.setName("Dirk"); + + PropertyPathStart start = new PropertyPathStart(); + start.setName("this"); + start.setContentType(ParameterContentType.FIELD); + + PropertyPath path = new PropertyPath(); + path.setName("surname"); + start.setPathToContinue(path); + + // name != surname -> exception + propertyAccessor.getPropertyContent(start, person, null, resultValueMock); + Mockito.verifyZeroInteractions(resultValueMock); + } + + @Test + public void analyzePersonParameter() throws PropertyAccessException { + // create initial object relation + Person peter = new Person("Peter"); + Person juergen = new Person("Juergen"); + peter.setChild(juergen); + Person hans = new Person("Hans"); + juergen.setChild(hans); + Person thomas = new Person("Thomas"); + hans.setChild(thomas); + Person michael = new Person("Michael"); + thomas.setChild(michael); + + // create the test path + PropertyPathStart start = new PropertyPathStart(); + start.setName("name"); + start.setSignaturePosition(1); + start.setContentType(ParameterContentType.PARAM); + + PropertyPath pathOne = new PropertyPath("child"); + start.setPathToContinue(pathOne); + + PropertyPath pathTwo = new PropertyPath("child"); + pathOne.setPathToContinue(pathTwo); + + PropertyPath pathThree = new PropertyPath("child"); + pathTwo.setPathToContinue(pathThree); + + PropertyPath pathFour = new PropertyPath("child"); + pathThree.setPathToContinue(pathFour); + + // set the parameter array + Object[] parameters = { null, peter }; + + String result = propertyAccessor.getPropertyContent(start, new Object(), parameters, resultValueMock); + assertThat(result, is("Michael")); + Mockito.verifyZeroInteractions(resultValueMock); + } + + @Test + public void removePropertyAccessorFromList() { + // create initial object relation + Person peter = new Person("Peter"); + Person juergen = new Person("Hans"); + peter.setChild(juergen); + + // CopyOnWriteArrayList for thread safety + List propertyAccessorList = new CopyOnWriteArrayList(); + + // valid + PropertyPathStart start = new PropertyPathStart(); + start.setName("name"); + start.setSignaturePosition(0); + start.setContentType(ParameterContentType.PARAM); + PropertyPath pathOne = new PropertyPath("child"); + start.setPathToContinue(pathOne); + propertyAccessorList.add(start); + + // not valid + start = new PropertyPathStart(); + start.setName("this"); + start.setContentType(ParameterContentType.FIELD); + pathOne = new PropertyPath("notValid"); + start.setPathToContinue(pathOne); + propertyAccessorList.add(start); + + // not valid as the second parameter will be null + start = new PropertyPathStart(); + start.setName("name"); + start.setSignaturePosition(1); + start.setContentType(ParameterContentType.PARAM); + pathOne = new PropertyPath("child"); + start.setPathToContinue(pathOne); + propertyAccessorList.add(start); + + assertThat(propertyAccessorList, hasSize(3)); + + List parameterContentList = propertyAccessor.getParameterContentData(propertyAccessorList, peter, new Object[] { peter }, resultValueMock); + + // size should be reduced to one + assertThat(propertyAccessorList, hasSize(1)); + // so is the size of the parameter content + assertThat(parameterContentList, is(notNullValue())); + assertThat(parameterContentList, hasSize(1)); + // changed due to xstream, the ' at the beginning will be always removed + // if displayed to the end-user. + assertThat(parameterContentList.get(0).getContent(), is("Hans")); + assertThat(parameterContentList.get(0).getSignaturePosition(), is(0)); + assertThat(parameterContentList.get(0).getName(), is("name")); + } + + @Test + public void invokeArrayLengthMethod() throws PropertyAccessException { + // create initial object relation + Person peter = new Person("Peter"); + String[] foreNames = new String[] { "Klaus", "Uwe" }; + peter.setForeNames(foreNames); + + PropertyPathStart start = new PropertyPathStart(); + start.setName("this"); + start.setContentType(ParameterContentType.FIELD); + + PropertyPath path = new PropertyPath(); + path.setName("foreNames"); + start.setPathToContinue(path); + + PropertyPath path2 = new PropertyPath(); + path2.setName("length()"); + path.setPathToContinue(path2); + + String result = propertyAccessor.getPropertyContent(start, peter, null, resultValueMock); + assertThat(Integer.parseInt(result), is(2)); + Mockito.verifyZeroInteractions(resultValueMock); + } + + @Test(expectedExceptions = { PropertyAccessException.class }) + public void invokeArrayLengthMethodOnNonArray() throws PropertyAccessException { + // create initial object relation + Person peter = new Person("Peter"); + String[] foreNames = new String[] { "Klaus", "Uwe" }; + peter.setForeNames(foreNames); + + PropertyPathStart start = new PropertyPathStart(); + start.setName("this"); + start.setContentType(ParameterContentType.FIELD); + + PropertyPath path = new PropertyPath(); + path.setName("name"); + start.setPathToContinue(path); + + PropertyPath path2 = new PropertyPath(); + path2.setName("length()"); + path.setPathToContinue(path2); + + // must result in an Exception as name is not an array + propertyAccessor.getPropertyContent(start, peter, null, resultValueMock); + Mockito.verifyZeroInteractions(resultValueMock); + } + + @Test + public void invokeListSizeMethod() throws PropertyAccessException { + // create initial object relation + Person peter = new Person("Peter"); + List foreNames = new ArrayList(); + foreNames.add("blub"); + foreNames.add("blub2"); + foreNames.add("blub3"); + peter.setForeNamesAsList(foreNames); + + PropertyPathStart start = new PropertyPathStart(); + start.setName("this"); + start.setContentType(ParameterContentType.FIELD); + + PropertyPath path = new PropertyPath(); + path.setName("foreNamesAsList"); + start.setPathToContinue(path); + + PropertyPath path2 = new PropertyPath(); + path2.setName("size()"); + path.setPathToContinue(path2); + + String result = propertyAccessor.getPropertyContent(start, peter, null, resultValueMock); + assertThat(Integer.parseInt(result), is(3)); + Mockito.verifyZeroInteractions(resultValueMock); + } + + @Test + public void analyzeReturnValueString() throws PropertyAccessException { + // valid + PropertyPathStart start = new PropertyPathStart(); + start.setName("returnName"); + start.setContentType(ParameterContentType.RETURN); + + String result = propertyAccessor.getPropertyContent(start, null, null, "Peter"); + assertThat(result, is("Peter")); + } + + @Test + public void analyzeReturnValueVoidMethod() throws PropertyAccessException { + // create initial object relation + Person peter = new Person("Peter"); + + // valid + PropertyPathStart start = new PropertyPathStart(); + start.setName("setName"); + start.setContentType(ParameterContentType.RETURN); + + String result = propertyAccessor.getPropertyContent(start, null, null, peter); + assertThat(result, is("Peter")); + } + + @Test + public void analyzeReturnValueObject() throws PropertyAccessException { + // create initial object relation + Person peter = new Person("Peter"); + Person juergen = new Person("Hans"); + peter.setChild(juergen); + + List propertyAccessorList = new ArrayList(); + + // valid + PropertyPathStart start = new PropertyPathStart(); + start.setName("return"); + start.setContentType(ParameterContentType.RETURN); + PropertyPath pathOne = new PropertyPath("child"); + start.setPathToContinue(pathOne); + propertyAccessorList.add(start); + + String result = propertyAccessor.getPropertyContent(start, null, null, peter); + assertThat(result, is("Hans")); + } + + @Test(expectedExceptions = { PropertyAccessException.class }) + public void invokeForbiddenMethod() throws PropertyAccessException { + // create initial object relation + Person peter = new Person("Peter"); + + PropertyPathStart start = new PropertyPathStart(); + start.setName("this"); + start.setContentType(ParameterContentType.FIELD); + + PropertyPath path = new PropertyPath(); + path.setName("getName()"); + start.setPathToContinue(path); + + propertyAccessor.getPropertyContent(start, peter, null, resultValueMock); + Mockito.verifyZeroInteractions(resultValueMock); + } + + @Test + public void concurentAccessOnPropertyAccessorList() { + + // create initial object relation + Person peter = new Person("Peter"); + Person juergen = new Person("Jurgen"); + peter.setChild(juergen); + + // CopyOnWriteArrayList for thread safety. It's the same as the propertyAccessorList in + // AbstractSensorConfig, as its operated on. In normal ArrayList, Fail-fast iterators throw + // ConcurrentModificationException on a best-effort basis. + // So it's not guaranteed, that there will be an exception on concurrent access, but the + // results will be inconsistent. + List propertyAccessorList = new CopyOnWriteArrayList(); + + // valid + PropertyPathStart start = new PropertyPathStart(); + start.setName("name"); + start.setSignaturePosition(0); + start.setContentType(ParameterContentType.PARAM); + PropertyPath pathOne = new PropertyPath("child"); + start.setPathToContinue(pathOne); + propertyAccessorList.add(start); + + // not valid + start = new PropertyPathStart(); + start.setName("this"); + start.setContentType(ParameterContentType.FIELD); + pathOne = new PropertyPath("notValid"); + start.setPathToContinue(pathOne); + propertyAccessorList.add(start); + + // not valid as the second parameter will be null + start = new PropertyPathStart(); + start.setName("name"); + start.setSignaturePosition(1); + start.setContentType(ParameterContentType.PARAM); + pathOne = new PropertyPath("child"); + start.setPathToContinue(pathOne); + propertyAccessorList.add(start); + + assertThat(propertyAccessorList, hasSize(3)); + + // Creating concurrent access + Iterator i = propertyAccessorList.iterator(); + // Access via iterator + i.next(); + // Direct access + List parameterContentList = propertyAccessor.getParameterContentData(propertyAccessorList, peter, new Object[] { peter }, null); + + // Double check results, in case of missing exception + // size should be reduced to one + assertThat(propertyAccessorList, hasSize(1)); + // so is the size of the parameter content + assertThat(parameterContentList, is(notNullValue())); + assertThat(parameterContentList, hasSize(1)); + // changed due to xstream, the ' at the beginning will be always removed + // if displayed to the end-user. + assertThat(parameterContentList.get(0).getContent(), is("Jurgen")); + assertThat(parameterContentList.get(0).getSignaturePosition(), is(0)); + assertThat(parameterContentList.get(0).getName(), is("name")); + + } + + @SuppressWarnings("unused") + private static class Person { + + private String name; + + private Person child; + + private String[] foreNames; + + private List foreNamesAsList; + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Person getChild() { + return child; + } + + public void setChild(Person child) { + this.child = child; + } + + public String[] getForeNames() { + return foreNames; + } + + public void setForeNames(String[] foreNames) { + this.foreNames = foreNames; + } + + public List getForeNamesAsList() { + return foreNamesAsList; + } + + public void setForeNamesAsList(List foreNamesAsList) { + this.foreNamesAsList = foreNamesAsList; + } + + @Override + public String toString() { + return name; + } + + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/core/impl/CoreServiceTest.java b/Agent/test/info/novatec/inspectit/agent/core/impl/CoreServiceTest.java new file mode 100644 index 000000000..6b5910318 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/core/impl/CoreServiceTest.java @@ -0,0 +1,386 @@ +package info.novatec.inspectit.agent.core.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.buffer.IBufferStrategy; +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.connection.IConnection; +import info.novatec.inspectit.agent.connection.ServerUnavailableException; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IObjectStorage; +import info.novatec.inspectit.agent.core.ListListener; +import info.novatec.inspectit.agent.sending.ISendingStrategy; +import info.novatec.inspectit.agent.sensor.method.timer.PlainTimerStorage; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.ExceptionEvent; +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.communication.SystemSensorData; +import info.novatec.inspectit.communication.data.CpuInformationData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.ParameterContentData; +import info.novatec.inspectit.communication.data.TimerData; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.mockito.Mock; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class CoreServiceTest extends AbstractLogSupport { + + @Mock + private IConfigurationStorage configurationStorage; + + @Mock + private IConnection connection; + + @SuppressWarnings("rawtypes") + @Mock + private IBufferStrategy bufferStrategy; + + @Mock + private ISendingStrategy sendingStrategy; + + @Mock + private IIdManager idManager; + + private CoreService coreService; + + @SuppressWarnings("unchecked") + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + List sendingStrategies = new ArrayList(); + sendingStrategies.add(sendingStrategy); + + coreService = new CoreService(configurationStorage, connection, bufferStrategy, sendingStrategies, idManager); + coreService.log = LoggerFactory.getLogger(CoreService.class); + } + + /** + * This method could fail if the testing machine is currently under heavy load. There is + * no reliable way to make this test always successful. + */ + @Test(enabled = false) + public void startStop() throws InterruptedException { + coreService.start(); + verify(sendingStrategy, times(1)).start(coreService); + + // have to wait one second to be sure that the getPlatformSensorTypes + // method should be called at least once + synchronized (this) { + wait(3000); + } + + coreService.stop(); + verify(sendingStrategy, times(1)).stop(); + verify(configurationStorage, atLeastOnce()).getPlatformSensorTypes(); + + verifyNoMoreInteractions(sendingStrategy, configurationStorage); + verifyZeroInteractions(connection, bufferStrategy, idManager); + } + + /** + * This method could also fail due to race conditions. + * + */ + @SuppressWarnings("unchecked") + @Test(dependsOnMethods = { "startStop" }, enabled = false) + public void sendOneMethodSensorData() throws InterruptedException, ServerUnavailableException { + coreService.start(); + + long sensorTypeId = 1; + long methodId = 5; + TimerData timerData = new TimerData(); + when(bufferStrategy.hasNext()).thenReturn(true).thenReturn(false); + List dataList = new ArrayList(); + dataList.add(timerData); + when(bufferStrategy.next()).thenReturn(dataList); + + coreService.addMethodSensorData(sensorTypeId, methodId, null, timerData); + coreService.sendData(); + + synchronized (this) { + wait(3000); + } + + verify(bufferStrategy, times(1)).addMeasurements(dataList); + verify(bufferStrategy, times(2)).hasNext(); + verify(bufferStrategy, times(1)).next(); + verify(bufferStrategy, times(1)).remove(); + + verify(connection, times(1)).sendDataObjects(dataList); + + verifyNoMoreInteractions(bufferStrategy, connection); + verifyZeroInteractions(idManager); + } + + /** + * This method could also fail due to race conditions. + * + */ + @SuppressWarnings("unchecked") + @Test(dependsOnMethods = { "startStop" }, enabled = false) + public void sendOnePlatformSensorData() throws InterruptedException, ServerUnavailableException { + coreService.start(); + + long sensorTypeId = 1; + CpuInformationData cpuInformationData = new CpuInformationData(); + when(bufferStrategy.hasNext()).thenReturn(true).thenReturn(false); + List dataList = new ArrayList(); + dataList.add(cpuInformationData); + when(bufferStrategy.next()).thenReturn(dataList); + + coreService.addPlatformSensorData(sensorTypeId, cpuInformationData); + coreService.sendData(); + + synchronized (this) { + wait(3000); + } + + verify(bufferStrategy, times(1)).addMeasurements(dataList); + verify(bufferStrategy, times(2)).hasNext(); + verify(bufferStrategy, times(1)).next(); + verify(bufferStrategy, times(1)).remove(); + + verify(connection, times(1)).sendDataObjects(dataList); + + verifyNoMoreInteractions(bufferStrategy, connection); + verifyZeroInteractions(idManager); + } + + @SuppressWarnings("unchecked") + @Test(dependsOnMethods = { "startStop" }, enabled = false) + public void sendOneExceptionSensorData() throws InterruptedException, ServerUnavailableException { + coreService.start(); + + long sensorTypeId = 1; + ExceptionSensorData exceptionSensorData = new ExceptionSensorData(); + exceptionSensorData.setThrowableIdentityHashCode(123456); + when(bufferStrategy.hasNext()).thenReturn(true).thenReturn(false); + List dataList = new ArrayList(); + dataList.add(exceptionSensorData); + when(bufferStrategy.next()).thenReturn(dataList); + + coreService.addExceptionSensorData(sensorTypeId, exceptionSensorData.getThrowableIdentityHashCode(), exceptionSensorData); + coreService.sendData(); + + synchronized (this) { + wait(3000); + } + + verify(bufferStrategy, times(1)).addMeasurements(dataList); + verify(bufferStrategy, times(2)).hasNext(); + verify(bufferStrategy, times(1)).next(); + verify(bufferStrategy, times(1)).remove(); + + verify(connection, times(1)).sendDataObjects(dataList); + + verifyNoMoreInteractions(bufferStrategy, connection); + verifyZeroInteractions(idManager); + } + + /** + * This method could also fail due to race conditions. + * + */ + @SuppressWarnings("unchecked") + @Test(dependsOnMethods = { "startStop" }, enabled = false) + public void sendOneObjectStorageData() throws InterruptedException, ServerUnavailableException { + coreService.start(); + + long sensorTypeId = 1; + long methodId = 5; + PlainTimerStorage timerStorage = new PlainTimerStorage(null, 0, sensorTypeId, methodId, Collections. emptyList(), false); + when(bufferStrategy.hasNext()).thenReturn(true).thenReturn(false); + List storageList = new ArrayList(); + storageList.add(timerStorage.finalizeDataObject()); + when(bufferStrategy.next()).thenReturn(storageList); + + coreService.addObjectStorage(sensorTypeId, methodId, null, timerStorage); + coreService.sendData(); + + synchronized (this) { + wait(3000); + } + + verify(bufferStrategy, times(1)).addMeasurements(storageList); + verify(bufferStrategy, times(2)).hasNext(); + verify(bufferStrategy, times(1)).next(); + verify(bufferStrategy, times(1)).remove(); + + verify(connection, times(1)).sendDataObjects(storageList); + + verifyNoMoreInteractions(bufferStrategy, connection); + verifyZeroInteractions(idManager); + } + + @SuppressWarnings("unchecked") + @Test + public void verifyListListenerMethodData() { + ListListener listener = mock(ListListener.class); + TimerData timerData = new TimerData(); + List dataList = new ArrayList(); + dataList.add(timerData); + + coreService.addListListener(listener); + coreService.addMethodSensorData(0, 0, null, timerData); + + verify(listener, times(1)).contentChanged(dataList); + + coreService.removeListListener(listener); + + verifyNoMoreInteractions(listener, bufferStrategy, connection, sendingStrategy); + verifyZeroInteractions(idManager); + } + + @SuppressWarnings("unchecked") + @Test + public void verifyListListenerPlatformData() { + ListListener listener = mock(ListListener.class); + CpuInformationData cpuInformationData = new CpuInformationData(); + List dataList = new ArrayList(); + dataList.add(cpuInformationData); + + coreService.addListListener(listener); + coreService.addPlatformSensorData(0, cpuInformationData); + + verify(listener, times(1)).contentChanged(dataList); + + coreService.removeListListener(listener); + + verifyNoMoreInteractions(listener, bufferStrategy, connection, sendingStrategy); + verifyZeroInteractions(idManager); + } + + @SuppressWarnings("unchecked") + @Test + public void verifyListListenerExceptionData() { + ListListener listener = mock(ListListener.class); + ExceptionSensorData exceptionSensorData = new ExceptionSensorData(); + exceptionSensorData.setThrowableType("MyException"); + exceptionSensorData.setThrowableIdentityHashCode(1234); + exceptionSensorData.setExceptionEvent(ExceptionEvent.CREATED); + List dataList = new ArrayList(); + dataList.add(exceptionSensorData); + + coreService.addListListener(listener); + coreService.addExceptionSensorData(0, exceptionSensorData.getThrowableIdentityHashCode(), exceptionSensorData); + + verify(listener, times(1)).contentChanged(dataList); + + coreService.removeListListener(listener); + + verifyNoMoreInteractions(listener, bufferStrategy, connection, sendingStrategy); + verifyZeroInteractions(idManager); + } + + @SuppressWarnings("unchecked") + @Test + public void verifyListListenerObjectStorageData() { + ListListener listener = mock(ListListener.class); + PlainTimerStorage timerStorage = new PlainTimerStorage(null, 0, 0, 0, Collections. emptyList(), false); + List storageList = new ArrayList(); + storageList.add(timerStorage); + + coreService.addListListener(listener); + coreService.addObjectStorage(0, 0, null, timerStorage); + + verify(listener, times(1)).contentChanged(storageList); + + coreService.removeListListener(listener); + + verifyNoMoreInteractions(listener, bufferStrategy, connection, sendingStrategy); + verifyZeroInteractions(idManager); + } + + @Test + public void addAndRetrieveMethodSensorDataNoPrefix() { + long sensorTypeId = 2; + long methodId = 5; + String prefix = null; + TimerData timerData = new TimerData(); + + coreService.addMethodSensorData(sensorTypeId, methodId, prefix, timerData); + + MethodSensorData methodSensorData = coreService.getMethodSensorData(sensorTypeId, methodId, prefix); + assertThat(methodSensorData, is(equalTo(((MethodSensorData) timerData)))); + } + + @Test + public void addAndRetrieveMethodSensorDataWithPrefix() { + long sensorTypeId = 2; + long methodId = 5; + String prefix = "prefix"; + TimerData timerData = new TimerData(); + + coreService.addMethodSensorData(sensorTypeId, methodId, prefix, timerData); + + MethodSensorData methodSensorData = coreService.getMethodSensorData(sensorTypeId, methodId, prefix); + assertThat(methodSensorData, is(equalTo(((MethodSensorData) timerData)))); + } + + @Test + public void addAndRetrievePlatformSensorData() { + long sensorTypeId = 4; + CpuInformationData cpuInformationData = new CpuInformationData(); + + coreService.addPlatformSensorData(sensorTypeId, cpuInformationData); + + SystemSensorData systemSensorData = coreService.getPlatformSensorData(sensorTypeId); + assertThat(systemSensorData, is(equalTo(((SystemSensorData) cpuInformationData)))); + } + + @Test + public void addAndRetrieveExceptionSensorData() { + long sensorTypeId = 10; + ExceptionSensorData exceptionSensorData = new ExceptionSensorData(); + exceptionSensorData.setThrowableType("MyException"); + exceptionSensorData.setThrowableIdentityHashCode(1234); + exceptionSensorData.setExceptionEvent(ExceptionEvent.CREATED); + + coreService.addExceptionSensorData(sensorTypeId, exceptionSensorData.getThrowableIdentityHashCode(), exceptionSensorData); + + MethodSensorData methodSensorData = coreService.getExceptionSensorData(sensorTypeId, exceptionSensorData.getThrowableIdentityHashCode()); + assertThat(methodSensorData, is(equalTo(((MethodSensorData) exceptionSensorData)))); + } + + @Test + public void addAndRetrieveObjectStorageDataNoPrefix() { + long sensorTypeId = 7; + long methodId = 10; + String prefix = null; + PlainTimerStorage timerStorage = new PlainTimerStorage(null, 0, 0, 0, Collections. emptyList(), false); + + coreService.addObjectStorage(sensorTypeId, methodId, prefix, timerStorage); + + IObjectStorage objectStorage = coreService.getObjectStorage(sensorTypeId, methodId, prefix); + assertThat(objectStorage, is(equalTo(((IObjectStorage) timerStorage)))); + } + + @Test + public void addAndRetrieveObjectStorageDataWithPrefix() { + long sensorTypeId = 7; + long methodId = 10; + String prefix = "prefiXX"; + PlainTimerStorage timerStorage = new PlainTimerStorage(null, 0, 0, 0, Collections. emptyList(), false); + + coreService.addObjectStorage(sensorTypeId, methodId, prefix, timerStorage); + + IObjectStorage objectStorage = coreService.getObjectStorage(sensorTypeId, methodId, prefix); + assertThat(objectStorage, is(equalTo(((IObjectStorage) timerStorage)))); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/core/impl/IdManagerTest.java b/Agent/test/info/novatec/inspectit/agent/core/impl/IdManagerTest.java new file mode 100644 index 000000000..452057204 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/core/impl/IdManagerTest.java @@ -0,0 +1,319 @@ +package info.novatec.inspectit.agent.core.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.config.impl.MethodSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.PlatformSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.config.impl.RepositoryConfig; +import info.novatec.inspectit.agent.connection.IConnection; +import info.novatec.inspectit.agent.connection.RegistrationException; +import info.novatec.inspectit.agent.connection.ServerUnavailableException; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.versioning.IVersioningService; + +import java.io.IOException; +import java.net.ConnectException; +import java.util.ArrayList; +import java.util.List; + +import org.mockito.Mock; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class IdManagerTest extends AbstractLogSupport { + + @Mock + private IConfigurationStorage configurationStorage; + + @Mock + private IConnection connection; + + @Mock + private IVersioningService versioning; + + private IdManager idManager; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + idManager = new IdManager(configurationStorage, connection, versioning); + idManager.log = LoggerFactory.getLogger(IdManager.class); + } + + /** + * This method could fail if the testing machine is currently under heavy load. There is + * no reliable way to make this test always successful. + */ + @Test + public void startStop() throws ConnectException, InterruptedException, ServerUnavailableException, RegistrationException { + String host = "localhost"; + int port = 1099; + RepositoryConfig repositoryConfig = new RepositoryConfig(host, port); + when(configurationStorage.getRepositoryConfig()).thenReturn(repositoryConfig); + String agentName = "testagent"; + when(configurationStorage.getAgentName()).thenReturn(agentName); + + idManager.start(); + + verify(configurationStorage, times(1)).getMethodSensorTypes(); + verify(configurationStorage, times(1)).getPlatformSensorTypes(); + + idManager.stop(); + } + + /** + * This method could fail if the testing machine is currently under heavy load. There is + * no reliable way to make this test always successful. + */ + @Test + public void connected() throws InterruptedException, ServerUnavailableException, RegistrationException { + when(connection.isConnected()).thenReturn(true); + + String host = "localhost"; + int port = 1099; + String agentName = "testagent"; + when(configurationStorage.getAgentName()).thenReturn(agentName); + RepositoryConfig repositoryConfig = new RepositoryConfig(host, port); + when(configurationStorage.getRepositoryConfig()).thenReturn(repositoryConfig); + + idManager.start(); + + verify(configurationStorage, times(1)).getMethodSensorTypes(); + verify(configurationStorage, times(1)).getPlatformSensorTypes(); + + idManager.stop(); + } + + @Test + public void connectAndRetrievePlatformId() throws ServerUnavailableException, RegistrationException, IdNotAvailableException, IOException { + RepositoryConfig repositoryConfig = mock(RepositoryConfig.class); + when(configurationStorage.getRepositoryConfig()).thenReturn(repositoryConfig); + when(configurationStorage.getAgentName()).thenReturn("testAgent"); + when(versioning.getVersion()).thenReturn("dummyVersion"); + + long fakePlatformId = 7L; + when(connection.isConnected()).thenReturn(false); + when(connection.registerPlatform("testAgent", "dummyVersion")).thenReturn(fakePlatformId); + + idManager.start(); + long platformId = idManager.getPlatformId(); + idManager.stop(); + + assertThat(platformId, is(equalTo(fakePlatformId))); + } + + @Test + public void retrievePlatformId() throws IdNotAvailableException, ServerUnavailableException, RegistrationException, InterruptedException, IOException { + long fakePlatformId = 3L; + when(connection.isConnected()).thenReturn(true); + when(connection.registerPlatform("testAgent", "dummyVersion")).thenReturn(fakePlatformId); + when(configurationStorage.getAgentName()).thenReturn("testAgent"); + when(versioning.getVersion()).thenReturn("dummyVersion"); + + idManager.start(); + long platformId = idManager.getPlatformId(); + idManager.stop(); + + assertThat(platformId, is(equalTo(fakePlatformId))); + } + + @Test(expectedExceptions = { IdNotAvailableException.class }) + public void platformIdNotAvailable() throws ConnectException, IdNotAvailableException { + RepositoryConfig repositoryConfig = mock(RepositoryConfig.class); + when(configurationStorage.getRepositoryConfig()).thenReturn(repositoryConfig); + when(connection.isConnected()).thenReturn(false); + doThrow(new ConnectException("fake")).when(connection).connect(anyString(), anyInt()); + + idManager.start(); + idManager.getPlatformId(); + } + + /** + * Tests that unregister of platform is executed if connection to the server is established and + * registration is performed. + */ + @Test + public void unregisterPlatform() throws ServerUnavailableException, RegistrationException, IOException, IdNotAvailableException { + // first simulate connect + long fakePlatformId = 3L; + when(connection.isConnected()).thenReturn(true); + when(connection.registerPlatform("testAgent", "dummyVersion")).thenReturn(fakePlatformId); + when(configurationStorage.getAgentName()).thenReturn("testAgent"); + when(versioning.getVersion()).thenReturn("dummyVersion"); + + idManager.start(); + idManager.getPlatformId(); + idManager.unregisterPlatform(); + + verify(connection, times(1)).unregisterPlatform("testAgent"); + } + + /** + * Test that unregister will not be called if there is no active connection to the server and + * registration is not done at first place. + */ + @Test + public void noUnregisterPlatform() throws RegistrationException { + // no unregister if no connection + when(connection.isConnected()).thenReturn(false); + idManager.unregisterPlatform(); + + // no unregister if registration is not done at the first place + when(connection.isConnected()).thenReturn(true); + idManager.unregisterPlatform(); + + verify(connection, times(0)).unregisterPlatform(anyString()); + } + + /** + * If unregister is called with shutdown initialized marker every next call to getPlatformId + * should throw an exception + */ + @Test(expectedExceptions = { IdNotAvailableException.class }) + public void unregisterPlatformAndInitShutdown() throws ServerUnavailableException, RegistrationException, IOException, IdNotAvailableException { + // first simulate connect + long fakePlatformId = 3L; + when(connection.isConnected()).thenReturn(true); + when(connection.registerPlatform("testAgent", "dummyVersion")).thenReturn(fakePlatformId); + when(configurationStorage.getAgentName()).thenReturn("testAgent"); + when(versioning.getVersion()).thenReturn("dummyVersion"); + + idManager.start(); + idManager.getPlatformId(); + idManager.unregisterPlatform(); + idManager.getPlatformId(); + } + + /** + * This method could fail if the testing machine is currently under heavy load. There is + * no reliable way to make this test always successful. + */ + @Test + public void registerMethodSensorTypes() throws InterruptedException, IdNotAvailableException { + RepositoryConfig repositoryConfig = mock(RepositoryConfig.class); + when(configurationStorage.getRepositoryConfig()).thenReturn(repositoryConfig); + when(configurationStorage.getAgentName()).thenReturn("testAgent"); + when(connection.isConnected()).thenReturn(true); + + MethodSensorTypeConfig methodSensorType = mock(MethodSensorTypeConfig.class); + List methodSensorTypes = new ArrayList(); + methodSensorTypes.add(methodSensorType); + when(configurationStorage.getMethodSensorTypes()).thenReturn(methodSensorTypes); + + idManager.start(); + assertThat(methodSensorType.getId(), is(0L)); + + synchronized (this) { + this.wait(2000L); + } + + assertThat(idManager.getRegisteredSensorTypeId(methodSensorType.getId()), is(not(-1L))); + + idManager.stop(); + } + + /** + * This method could fail if the testing machine is currently under heavy load. There is + * no reliable way to make this test always successful. + */ + @Test + public void registerPlatformSensorTypes() throws InterruptedException, IdNotAvailableException { + RepositoryConfig repositoryConfig = mock(RepositoryConfig.class); + when(configurationStorage.getRepositoryConfig()).thenReturn(repositoryConfig); + when(configurationStorage.getAgentName()).thenReturn("testAgent"); + when(connection.isConnected()).thenReturn(true); + + PlatformSensorTypeConfig platformSensorType = mock(PlatformSensorTypeConfig.class); + List platformSensorTypes = new ArrayList(); + platformSensorTypes.add(platformSensorType); + when(configurationStorage.getPlatformSensorTypes()).thenReturn(platformSensorTypes); + + idManager.start(); + assertThat(platformSensorType.getId(), is(0L)); + + synchronized (this) { + this.wait(2000L); + } + + assertThat(idManager.getRegisteredSensorTypeId(platformSensorType.getId()), is(0L)); + + idManager.stop(); + } + + @Test(expectedExceptions = { IdNotAvailableException.class }) + public void sensorTypeIdNotAvailable() throws InterruptedException, IdNotAvailableException, ConnectException { + RepositoryConfig repositoryConfig = mock(RepositoryConfig.class); + when(configurationStorage.getRepositoryConfig()).thenReturn(repositoryConfig); + when(connection.isConnected()).thenReturn(false); + doThrow(new ConnectException("fake")).when(connection).connect(anyString(), anyInt()); + + MethodSensorTypeConfig methodSensorType = mock(MethodSensorTypeConfig.class); + List methodSensorTypes = new ArrayList(); + methodSensorTypes.add(methodSensorType); + when(configurationStorage.getMethodSensorTypes()).thenReturn(methodSensorTypes); + + idManager.start(); + assertThat(methodSensorType.getId(), is(0L)); + + idManager.getRegisteredSensorTypeId(methodSensorType.getId()); + } + + @Test + public void registerMethod() throws ConnectException, ServerUnavailableException, RegistrationException, IdNotAvailableException { + RepositoryConfig repositoryConfig = mock(RepositoryConfig.class); + when(configurationStorage.getRepositoryConfig()).thenReturn(repositoryConfig); + when(connection.isConnected()).thenReturn(true); + + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + + idManager.start(); + + when(connection.registerMethod(anyInt(), eq(registeredSensorConfig))).thenReturn(7L).thenReturn(13L); + long id = idManager.registerMethod(registeredSensorConfig); + assertThat(id, is(greaterThanOrEqualTo(0L))); + assertThat(idManager.getRegisteredMethodId(id), is(7L)); + + id = idManager.registerMethod(registeredSensorConfig); + assertThat(id, is(greaterThanOrEqualTo(0L))); + assertThat(idManager.getRegisteredMethodId(id), is(13L)); + + idManager.stop(); + } + + @Test + public void testMapping() throws ServerUnavailableException, RegistrationException { + RepositoryConfig repositoryConfig = mock(RepositoryConfig.class); + when(configurationStorage.getRepositoryConfig()).thenReturn(repositoryConfig); + when(connection.isConnected()).thenReturn(true); + + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + MethodSensorTypeConfig methodSensorType = mock(MethodSensorTypeConfig.class); + + idManager.start(); + + when(connection.registerMethod(anyInt(), eq(registeredSensorConfig))).thenReturn(7L); + when(connection.registerMethodSensorType(anyInt(), eq(methodSensorType))).thenReturn(5L); + long methodId = idManager.registerMethod(registeredSensorConfig); + long sensorTypeId = idManager.registerMethodSensorType(methodSensorType); + idManager.addSensorTypeToMethod(sensorTypeId, methodId); + + idManager.stop(); + + verify(connection).addSensorTypeToMethod(5L, 7L); + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/hooking/impl/HookDispatcherTest.java b/Agent/test/info/novatec/inspectit/agent/hooking/impl/HookDispatcherTest.java new file mode 100644 index 000000000..0e61899e7 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/hooking/impl/HookDispatcherTest.java @@ -0,0 +1,776 @@ +package info.novatec.inspectit.agent.hooking.impl; + +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.analyzer.classes.MyTestException; +import info.novatec.inspectit.agent.config.impl.MethodSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.hooking.IConstructorHook; +import info.novatec.inspectit.agent.hooking.IHook; +import info.novatec.inspectit.agent.hooking.IMethodHook; +import info.novatec.inspectit.agent.sensor.exception.ExceptionSensorHook; +import info.novatec.inspectit.agent.sensor.method.IMethodSensor; +import info.novatec.inspectit.agent.sensor.method.invocationsequence.InvocationSequenceHook; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.mockito.InOrder; +import org.mockito.Mock; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class HookDispatcherTest extends AbstractLogSupport { + + @Mock + private ICoreService coreService; + + private HookDispatcher hookDispatcher; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { + hookDispatcher = new HookDispatcher(coreService); + hookDispatcher.log = LoggerFactory.getLogger(HookDispatcher.class); + } + + @Test + public void dispatchNoMethodHooks() { + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + int methodId = 3; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object returnValue = mock(Object.class); + + hookDispatcher.addMethodMapping(methodId, registeredSensorConfig); + + hookDispatcher.dispatchMethodBeforeBody(methodId, object, parameters); + verify(registeredSensorConfig, times(1)).startsInvocationSequence(); + verify(registeredSensorConfig, times(1)).getReverseMethodHooks(); + + hookDispatcher.dispatchFirstMethodAfterBody(methodId, object, parameters, returnValue); + verify(registeredSensorConfig, times(1)).getMethodHooks(); + + hookDispatcher.dispatchSecondMethodAfterBody(methodId, object, parameters, returnValue); + verify(registeredSensorConfig, times(2)).startsInvocationSequence(); + verify(registeredSensorConfig, times(2)).getMethodHooks(); + + verifyZeroInteractions(object, coreService, returnValue); + verifyNoMoreInteractions(registeredSensorConfig); + } + + @Test + public void dispatchOneMethodHookWithoutInvocationTrace() { + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Map methodHooks = new LinkedHashMap(); + IMethodHook methodHook = mock(IMethodHook.class); + long sensorTypeId = 7L; + methodHooks.put(sensorTypeId, methodHook); + when(registeredSensorConfig.getReverseMethodHooks()).thenReturn(methodHooks); + when(registeredSensorConfig.getMethodHooks()).thenReturn(methodHooks); + + int methodId = 3; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object returnValue = mock(Object.class); + + hookDispatcher.addMethodMapping(methodId, registeredSensorConfig); + + hookDispatcher.dispatchMethodBeforeBody(methodId, object, parameters); + verify(registeredSensorConfig, times(1)).startsInvocationSequence(); + verify(registeredSensorConfig, times(1)).getReverseMethodHooks(); + verify(methodHook, times(1)).beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + + hookDispatcher.dispatchFirstMethodAfterBody(methodId, object, parameters, returnValue); + verify(registeredSensorConfig, times(1)).getMethodHooks(); + verify(methodHook, times(1)).firstAfterBody(methodId, sensorTypeId, object, parameters, returnValue, registeredSensorConfig); + + hookDispatcher.dispatchSecondMethodAfterBody(methodId, object, parameters, returnValue); + verify(registeredSensorConfig, times(2)).startsInvocationSequence(); + verify(registeredSensorConfig, times(2)).getMethodHooks(); + verify(methodHook, times(1)).secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, returnValue, registeredSensorConfig); + + verifyZeroInteractions(object, coreService, returnValue); + verifyNoMoreInteractions(registeredSensorConfig, methodHook); + } + + @Test + public void dispatchManyMethodHooksWithoutInvocationTrace() { + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Map methodHooks = new LinkedHashMap(); + Map reverseMethodHooks = new LinkedHashMap(); + IMethodHook methodHookOne = mock(IMethodHook.class); + IMethodHook methodHookTwo = mock(IMethodHook.class); + IMethodHook methodHookThree = mock(IMethodHook.class); + long sensorTypeIdOne = 7L; + long sensorTypeIdTwo = 13L; + long sensorTypeIdThree = 15L; + methodHooks.put(sensorTypeIdOne, methodHookOne); + methodHooks.put(sensorTypeIdTwo, methodHookTwo); + methodHooks.put(sensorTypeIdThree, methodHookThree); + reverseMethodHooks.put(sensorTypeIdThree, methodHookThree); + reverseMethodHooks.put(sensorTypeIdTwo, methodHookTwo); + reverseMethodHooks.put(sensorTypeIdOne, methodHookOne); + when(registeredSensorConfig.getMethodHooks()).thenReturn(methodHooks); + when(registeredSensorConfig.getReverseMethodHooks()).thenReturn(reverseMethodHooks); + + int methodId = 3; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object returnValue = mock(Object.class); + + hookDispatcher.addMethodMapping(methodId, registeredSensorConfig); + + hookDispatcher.dispatchMethodBeforeBody(methodId, object, parameters); + verify(registeredSensorConfig, times(1)).startsInvocationSequence(); + verify(registeredSensorConfig, times(1)).getReverseMethodHooks(); + InOrder inOrder = inOrder(methodHookOne, methodHookTwo, methodHookThree); + inOrder.verify(methodHookThree, times(1)).beforeBody(methodId, sensorTypeIdThree, object, parameters, registeredSensorConfig); + inOrder.verify(methodHookTwo, times(1)).beforeBody(methodId, sensorTypeIdTwo, object, parameters, registeredSensorConfig); + inOrder.verify(methodHookOne, times(1)).beforeBody(methodId, sensorTypeIdOne, object, parameters, registeredSensorConfig); + + hookDispatcher.dispatchFirstMethodAfterBody(methodId, object, parameters, returnValue); + verify(registeredSensorConfig, times(1)).getMethodHooks(); + inOrder = inOrder(methodHookOne, methodHookTwo, methodHookThree); + inOrder.verify(methodHookOne, times(1)).firstAfterBody(methodId, sensorTypeIdOne, object, parameters, returnValue, registeredSensorConfig); + inOrder.verify(methodHookTwo, times(1)).firstAfterBody(methodId, sensorTypeIdTwo, object, parameters, returnValue, registeredSensorConfig); + inOrder.verify(methodHookThree, times(1)).firstAfterBody(methodId, sensorTypeIdThree, object, parameters, returnValue, registeredSensorConfig); + + hookDispatcher.dispatchSecondMethodAfterBody(methodId, object, parameters, returnValue); + verify(registeredSensorConfig, times(2)).startsInvocationSequence(); + verify(registeredSensorConfig, times(2)).getMethodHooks(); + inOrder = inOrder(methodHookOne, methodHookTwo, methodHookThree); + inOrder.verify(methodHookOne, times(1)).secondAfterBody(coreService, methodId, sensorTypeIdOne, object, parameters, returnValue, registeredSensorConfig); + inOrder.verify(methodHookTwo, times(1)).secondAfterBody(coreService, methodId, sensorTypeIdTwo, object, parameters, returnValue, registeredSensorConfig); + inOrder.verify(methodHookThree, times(1)).secondAfterBody(coreService, methodId, sensorTypeIdThree, object, parameters, returnValue, registeredSensorConfig); + + verifyZeroInteractions(object, coreService, returnValue); + verifyNoMoreInteractions(registeredSensorConfig, methodHookOne); + } + + @Test + public void dispatchOneMethodHookWithInvocationTrace() { + // create registered sensor configuration which starts an invocation + // sequence + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + when(registeredSensorConfig.startsInvocationSequence()).thenReturn(true); + MethodSensorTypeConfig invocSensorType = mock(MethodSensorTypeConfig.class); + when(registeredSensorConfig.getInvocationSequenceSensorTypeConfig()).thenReturn(invocSensorType); + IMethodSensor methodSensor = mock(IMethodSensor.class); + when(invocSensorType.getSensorType()).thenReturn(methodSensor); + long invocSensorTypeId = 13L; + InvocationSequenceHook invocHook = mock(InvocationSequenceHook.class); + when(methodSensor.getHook()).thenReturn(invocHook); + + // create method hooks map + Map methodHooks = new LinkedHashMap(); + IMethodHook methodHook = mock(IMethodHook.class); + long methodSensorTypeId = 7L; + methodHooks.put(methodSensorTypeId, methodHook); + methodHooks.put(invocSensorTypeId, invocHook); + when(registeredSensorConfig.getReverseMethodHooks()).thenReturn(methodHooks); + when(registeredSensorConfig.getMethodHooks()).thenReturn(methodHooks); + + long methodId = 3L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object returnValue = mock(Object.class); + + // map the first method + hookDispatcher.addMethodMapping(methodId, registeredSensorConfig); + + RegisteredSensorConfig registeredSensorConfigTwo = mock(RegisteredSensorConfig.class); + Map methodHooksTwo = new LinkedHashMap(); + methodHooksTwo.put(methodSensorTypeId, methodHook); + when(registeredSensorConfigTwo.getReverseMethodHooks()).thenReturn(methodHooksTwo); + when(registeredSensorConfigTwo.getMethodHooks()).thenReturn(methodHooksTwo); + long methodIdTwo = 15L; + // map the second method + hookDispatcher.addMethodMapping(methodIdTwo, registeredSensorConfigTwo); + + // //////////////////////////////////////////////////////// + // FIRST METHOD DISPATCHER + + // dispatch the first method - before body + hookDispatcher.dispatchMethodBeforeBody(methodId, object, parameters); + verify(registeredSensorConfig, times(1)).startsInvocationSequence(); + verify(registeredSensorConfig, times(1)).getReverseMethodHooks(); + verify(registeredSensorConfig, times(1)).getInvocationSequenceSensorTypeConfig(); + verify(methodSensor, times(1)).getHook(); + verify(methodHook, times(1)).beforeBody(methodId, methodSensorTypeId, object, parameters, registeredSensorConfig); + verify(invocHook, times(1)).beforeBody(methodId, invocSensorTypeId, object, parameters, registeredSensorConfig); + verify(invocSensorType, times(1)).getSensorType(); + + // //////////////////////////////////////////////////////// + // SECOND METHOD DISPATCHER + + // dispatch the second method - before body + hookDispatcher.dispatchMethodBeforeBody(methodIdTwo, object, parameters); + verify(registeredSensorConfig, times(1)).startsInvocationSequence(); + verify(registeredSensorConfig, times(1)).getReverseMethodHooks(); + verify(methodSensor, times(1)).getHook(); + verify(methodHook, times(1)).beforeBody(methodIdTwo, methodSensorTypeId, object, parameters, registeredSensorConfigTwo); + verify(invocHook, times(1)).beforeBody(eq(methodIdTwo), anyLong(), eq(object), eq(parameters), eq(registeredSensorConfigTwo)); + verify(invocSensorType, times(1)).getSensorType(); + + // dispatch the second method - first after body + hookDispatcher.dispatchFirstMethodAfterBody(methodIdTwo, object, parameters, returnValue); + verify(registeredSensorConfigTwo, times(1)).getMethodHooks(); + verify(methodHook, times(1)).firstAfterBody(methodIdTwo, methodSensorTypeId, object, parameters, returnValue, registeredSensorConfigTwo); + + // dispatch the second method - second after body + hookDispatcher.dispatchSecondMethodAfterBody(methodIdTwo, object, parameters, returnValue); + verify(registeredSensorConfigTwo, times(2)).startsInvocationSequence(); + verify(registeredSensorConfigTwo, times(2)).getMethodHooks(); + verify(methodHook, times(1)).secondAfterBody(invocHook, methodIdTwo, methodSensorTypeId, object, parameters, returnValue, registeredSensorConfigTwo); + verify(invocHook, times(1)).secondAfterBody(eq(coreService), eq(methodIdTwo), anyLong(), eq(object), eq(parameters), eq(returnValue), eq(registeredSensorConfigTwo)); + + // END SECOND METHOD DISPATCHER + // //////////////////////////////////////////////////////// + + // dispatch the first method - first after body + hookDispatcher.dispatchFirstMethodAfterBody(methodId, object, parameters, returnValue); + verify(registeredSensorConfig, times(1)).getMethodHooks(); + verify(methodHook, times(1)).firstAfterBody(methodId, methodSensorTypeId, object, parameters, returnValue, registeredSensorConfig); + verify(invocHook, times(1)).firstAfterBody(methodId, invocSensorTypeId, object, parameters, returnValue, registeredSensorConfig); + + // dispatch the first method - second after body + hookDispatcher.dispatchSecondMethodAfterBody(methodId, object, parameters, returnValue); + verify(registeredSensorConfig, times(2)).startsInvocationSequence(); + verify(registeredSensorConfig, times(2)).getMethodHooks(); + verify(methodHook, times(1)).secondAfterBody(invocHook, methodId, methodSensorTypeId, object, parameters, returnValue, registeredSensorConfig); + verify(invocHook, times(1)).secondAfterBody(coreService, methodId, invocSensorTypeId, object, parameters, returnValue, registeredSensorConfig); + + // END FIRST METHOD DISPATCHER + // //////////////////////////////////////////////////////// + + // verify that no further interactions happened + verifyZeroInteractions(object, coreService, returnValue, invocHook); + verifyNoMoreInteractions(registeredSensorConfig, methodHook, methodSensor, invocSensorType); + } + + @Test + public void dispatchNoConstructorHooks() { + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + int methodId = 3; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + + hookDispatcher.addConstructorMapping(methodId, registeredSensorConfig); + + hookDispatcher.dispatchConstructorBeforeBody(methodId, parameters); + verify(registeredSensorConfig, times(1)).startsInvocationSequence(); + verify(registeredSensorConfig, times(1)).getReverseMethodHooks(); + + hookDispatcher.dispatchConstructorAfterBody(methodId, object, parameters); + verify(registeredSensorConfig, times(2)).startsInvocationSequence(); + verify(registeredSensorConfig, times(1)).getMethodHooks(); + + verifyZeroInteractions(object, coreService); + verifyNoMoreInteractions(registeredSensorConfig); + } + + @Test + public void dispatchOneConstructorHookWithoutInvocationTrace() { + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Map constructorHooks = new LinkedHashMap(); + IConstructorHook constructorHook = mock(IConstructorHook.class); + long sensorTypeId = 7L; + constructorHooks.put(sensorTypeId, constructorHook); + when(registeredSensorConfig.getReverseMethodHooks()).thenReturn(constructorHooks); + when(registeredSensorConfig.getMethodHooks()).thenReturn(constructorHooks); + + int methodId = 3; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + + hookDispatcher.addConstructorMapping(methodId, registeredSensorConfig); + + hookDispatcher.dispatchConstructorBeforeBody(methodId, parameters); + verify(registeredSensorConfig, times(1)).startsInvocationSequence(); + verify(registeredSensorConfig, times(1)).getReverseMethodHooks(); + verify(constructorHook, times(1)).beforeConstructor(methodId, sensorTypeId, parameters, registeredSensorConfig); + + hookDispatcher.dispatchConstructorAfterBody(methodId, object, parameters); + verify(registeredSensorConfig, times(2)).startsInvocationSequence(); + verify(registeredSensorConfig, times(1)).getMethodHooks(); + verify(constructorHook, times(1)).afterConstructor(coreService, methodId, sensorTypeId, object, parameters, registeredSensorConfig); + + verifyZeroInteractions(object, coreService); + verifyNoMoreInteractions(registeredSensorConfig, constructorHook); + } + + @Test + public void dispatchManyConstructorHooksWithoutInvocationTrace() { + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Map constructorHooks = new LinkedHashMap(); + Map reverseConstructorHooks = new LinkedHashMap(); + IConstructorHook constructorHookOne = mock(IConstructorHook.class); + IConstructorHook constructorHookTwo = mock(IConstructorHook.class); + IConstructorHook constructorHookThree = mock(IConstructorHook.class); + long sensorTypeIdOne = 7L; + long sensorTypeIdTwo = 13L; + long sensorTypeIdThree = 15L; + constructorHooks.put(sensorTypeIdOne, constructorHookOne); + constructorHooks.put(sensorTypeIdTwo, constructorHookTwo); + constructorHooks.put(sensorTypeIdThree, constructorHookThree); + reverseConstructorHooks.put(sensorTypeIdThree, constructorHookThree); + reverseConstructorHooks.put(sensorTypeIdTwo, constructorHookTwo); + reverseConstructorHooks.put(sensorTypeIdOne, constructorHookOne); + when(registeredSensorConfig.getMethodHooks()).thenReturn(constructorHooks); + when(registeredSensorConfig.getReverseMethodHooks()).thenReturn(reverseConstructorHooks); + + int methodId = 3; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + + hookDispatcher.addConstructorMapping(methodId, registeredSensorConfig); + + hookDispatcher.dispatchConstructorBeforeBody(methodId, parameters); + verify(registeredSensorConfig, times(1)).startsInvocationSequence(); + verify(registeredSensorConfig, times(1)).getReverseMethodHooks(); + InOrder inOrder = inOrder(constructorHookOne, constructorHookTwo, constructorHookThree); + inOrder.verify(constructorHookThree, times(1)).beforeConstructor(methodId, sensorTypeIdThree, parameters, registeredSensorConfig); + inOrder.verify(constructorHookTwo, times(1)).beforeConstructor(methodId, sensorTypeIdTwo, parameters, registeredSensorConfig); + inOrder.verify(constructorHookOne, times(1)).beforeConstructor(methodId, sensorTypeIdOne, parameters, registeredSensorConfig); + + hookDispatcher.dispatchConstructorAfterBody(methodId, object, parameters); + verify(registeredSensorConfig, times(2)).startsInvocationSequence(); + verify(registeredSensorConfig, times(1)).getMethodHooks(); + inOrder = inOrder(constructorHookOne, constructorHookTwo, constructorHookThree); + inOrder.verify(constructorHookOne, times(1)).afterConstructor(coreService, methodId, sensorTypeIdOne, object, parameters, registeredSensorConfig); + inOrder.verify(constructorHookTwo, times(1)).afterConstructor(coreService, methodId, sensorTypeIdTwo, object, parameters, registeredSensorConfig); + inOrder.verify(constructorHookThree, times(1)).afterConstructor(coreService, methodId, sensorTypeIdThree, object, parameters, registeredSensorConfig); + + verifyZeroInteractions(object, coreService); + verifyNoMoreInteractions(registeredSensorConfig, constructorHookOne); + } + + @Test + public void dispatchOneConstructorHookWithInvocationTrace() { + // create registered sensor configuration which starts an invocation + // sequence + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + when(registeredSensorConfig.startsInvocationSequence()).thenReturn(true); + MethodSensorTypeConfig invocSensorType = mock(MethodSensorTypeConfig.class); + when(registeredSensorConfig.getInvocationSequenceSensorTypeConfig()).thenReturn(invocSensorType); + IMethodSensor methodSensor = mock(IMethodSensor.class); + when(invocSensorType.getSensorType()).thenReturn(methodSensor); + long invocSensorTypeId = 13L; + InvocationSequenceHook invocHook = mock(InvocationSequenceHook.class); + when(methodSensor.getHook()).thenReturn(invocHook); + + // create method hooks map + Map hooks = new LinkedHashMap(); + IConstructorHook constructorHook = mock(IConstructorHook.class); + long methodSensorTypeId = 7L; + hooks.put(invocSensorTypeId, invocHook); + when(registeredSensorConfig.getReverseMethodHooks()).thenReturn(hooks); + when(registeredSensorConfig.getMethodHooks()).thenReturn(hooks); + + long methodId = 3L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object returnValue = mock(Object.class); + + // map the first method + hookDispatcher.addMethodMapping(methodId, registeredSensorConfig); + + RegisteredSensorConfig registeredSensorConfigTwo = mock(RegisteredSensorConfig.class); + Map methodHooksTwo = new LinkedHashMap(); + methodHooksTwo.put(methodSensorTypeId, constructorHook); + when(registeredSensorConfigTwo.getReverseMethodHooks()).thenReturn(methodHooksTwo); + when(registeredSensorConfigTwo.getMethodHooks()).thenReturn(methodHooksTwo); + long methodIdTwo = 15L; + // map the second method + hookDispatcher.addConstructorMapping(methodIdTwo, registeredSensorConfigTwo); + + // //////////////////////////////////////////////////////// + // METHOD DISPATCHER + + // dispatch the first method - before body + hookDispatcher.dispatchMethodBeforeBody(methodId, object, parameters); + verify(registeredSensorConfig, times(1)).startsInvocationSequence(); + verify(registeredSensorConfig, times(1)).getReverseMethodHooks(); + verify(registeredSensorConfig, times(1)).getInvocationSequenceSensorTypeConfig(); + verify(methodSensor, times(1)).getHook(); + verify(invocHook, times(1)).beforeBody(methodId, invocSensorTypeId, object, parameters, registeredSensorConfig); + verify(invocSensorType, times(1)).getSensorType(); + + // //////////////////////////////////////////////////////// + // CONSTRUCTOR DISPATCHER + + // dispatch the constructor - before constructor + hookDispatcher.dispatchConstructorBeforeBody(methodIdTwo, parameters); + verify(registeredSensorConfig, times(1)).startsInvocationSequence(); + verify(registeredSensorConfig, times(1)).getReverseMethodHooks(); + verify(methodSensor, times(1)).getHook(); + verify(constructorHook, times(1)).beforeConstructor(methodIdTwo, methodSensorTypeId, parameters, registeredSensorConfigTwo); + verify((IConstructorHook) invocHook, times(1)).beforeConstructor(eq(methodIdTwo), anyLong(), eq(parameters), eq(registeredSensorConfigTwo)); + verify(invocSensorType, times(1)).getSensorType(); + + // dispatch the constructor - after constructor + hookDispatcher.dispatchConstructorAfterBody(methodIdTwo, object, parameters); + verify(registeredSensorConfigTwo, times(2)).startsInvocationSequence(); + verify(registeredSensorConfigTwo, times(1)).getMethodHooks(); + verify(constructorHook, times(1)).afterConstructor(invocHook, methodIdTwo, methodSensorTypeId, object, parameters, registeredSensorConfigTwo); + verify((IConstructorHook) invocHook, times(1)).afterConstructor(eq(coreService), eq(methodIdTwo), anyLong(), eq(object), eq(parameters), eq(registeredSensorConfigTwo)); + + // END CONSTRUCTOR DISPATCHER + // //////////////////////////////////////////////////////// + + // dispatch the method - first after body + hookDispatcher.dispatchFirstMethodAfterBody(methodId, object, parameters, returnValue); + verify(registeredSensorConfig, times(1)).getMethodHooks(); + verify(invocHook, times(1)).firstAfterBody(methodId, invocSensorTypeId, object, parameters, returnValue, registeredSensorConfig); + + // dispatch the method - second after body + hookDispatcher.dispatchSecondMethodAfterBody(methodId, object, parameters, returnValue); + verify(registeredSensorConfig, times(2)).startsInvocationSequence(); + verify(registeredSensorConfig, times(2)).getMethodHooks(); + verify(invocHook, times(1)).secondAfterBody(coreService, methodId, invocSensorTypeId, object, parameters, returnValue, registeredSensorConfig); + + // END METHOD DISPATCHER + // //////////////////////////////////////////////////////// + + // verify that no further interactions happened + verifyZeroInteractions(object, coreService, returnValue, invocHook); + verifyNoMoreInteractions(registeredSensorConfig, constructorHook, methodSensor, invocSensorType); + } + + @Test + public void dispatchExceptionSensorWithOneMethodHookWithoutInvocationTrace() throws Exception { + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + RegisteredSensorConfig registeredConstructorSensorConfig = mock(RegisteredSensorConfig.class); + + long sensorTypeId = 7L; + long exceptionSensorTypeId = 10L; + long methodId = 3L; + long constructorId = 7L; + + // the exception sensor type config + MethodSensorTypeConfig sensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(sensorTypeConfig.getName()).thenReturn("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"); + when(sensorTypeConfig.getId()).thenReturn(exceptionSensorTypeId); + + // the exception sensor hook + IMethodSensor exceptionSensor = mock(IMethodSensor.class); + when(sensorTypeConfig.getSensorType()).thenReturn(exceptionSensor); + ExceptionSensorHook exceptionHook = mock(ExceptionSensorHook.class); + when(exceptionSensor.getHook()).thenReturn(exceptionHook); + + // the map for the method hooks + Map methodHooks = new LinkedHashMap(); + IMethodHook methodHook = mock(IMethodHook.class); + // the map for the constructor hooks + Map constructorHooks = new LinkedHashMap(); + methodHooks.put(sensorTypeId, methodHook); + constructorHooks.put(exceptionSensorTypeId, exceptionHook); + when(registeredSensorConfig.getReverseMethodHooks()).thenReturn(methodHooks); + when(registeredSensorConfig.getMethodHooks()).thenReturn(methodHooks); + + when(registeredSensorConfig.getExceptionSensorTypeConfig()).thenReturn(sensorTypeConfig); + when(registeredConstructorSensorConfig.getExceptionSensorTypeConfig()).thenReturn(sensorTypeConfig); + when(registeredConstructorSensorConfig.getReverseMethodHooks()).thenReturn(constructorHooks); + when(registeredConstructorSensorConfig.getMethodHooks()).thenReturn(constructorHooks); + + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object returnValue = mock(Object.class); + Object exceptionObject = mock(MyTestException.class); + + hookDispatcher.addMethodMapping(methodId, registeredSensorConfig); + hookDispatcher.addConstructorMapping(constructorId, registeredConstructorSensorConfig); + + hookDispatcher.dispatchMethodBeforeBody(methodId, object, parameters); + verify(registeredSensorConfig, times(1)).startsInvocationSequence(); + verify(registeredSensorConfig, times(1)).getReverseMethodHooks(); + verify(methodHook, times(1)).beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + + hookDispatcher.dispatchConstructorBeforeBody(constructorId, parameters); + verify(exceptionHook, times(1)).beforeConstructor(constructorId, exceptionSensorTypeId, parameters, registeredConstructorSensorConfig); + + // first method of exception sensor + hookDispatcher.dispatchConstructorAfterBody(constructorId, exceptionObject, parameters); + verify(exceptionHook, times(1)).afterConstructor(coreService, constructorId, exceptionSensorTypeId, exceptionObject, parameters, registeredConstructorSensorConfig); + + // second method of exception sensor + hookDispatcher.dispatchOnThrowInBody(methodId, object, parameters, exceptionObject); + verify(registeredSensorConfig, times(2)).getExceptionSensorTypeConfig(); + verify(exceptionHook, times(1)).dispatchOnThrowInBody(coreService, methodId, exceptionSensorTypeId, object, exceptionObject, parameters, registeredSensorConfig); + + hookDispatcher.dispatchFirstMethodAfterBody(methodId, object, parameters, returnValue); + verify(registeredSensorConfig, times(1)).getMethodHooks(); + verify(methodHook, times(1)).firstAfterBody(methodId, sensorTypeId, object, parameters, returnValue, registeredSensorConfig); + + hookDispatcher.dispatchSecondMethodAfterBody(methodId, object, parameters, returnValue); + verify(registeredSensorConfig, times(2)).startsInvocationSequence(); + verify(registeredSensorConfig, times(2)).getMethodHooks(); + verify(methodHook, times(1)).secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, returnValue, registeredSensorConfig); + + // third method of exception sensor + hookDispatcher.dispatchBeforeCatch(methodId, exceptionObject); + verify(registeredSensorConfig, times(4)).getExceptionSensorTypeConfig(); + verify(exceptionHook, times(1)).dispatchBeforeCatchBody(coreService, methodId, exceptionSensorTypeId, exceptionObject, registeredSensorConfig); + + verifyZeroInteractions(object, coreService, returnValue); + verifyNoMoreInteractions(methodHook, exceptionHook, registeredSensorConfig); + } + + @Test + public void dispatchExceptionSensorWithManyMethodHooksWithoutInvocationTrace() throws Exception { + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + RegisteredSensorConfig registeredConstructorSensorConfig = mock(RegisteredSensorConfig.class); + + // the map for the method hooks + Map methodHooks = new LinkedHashMap(); + Map reverseMethodHooks = new LinkedHashMap(); + IMethodHook methodHookOne = mock(IMethodHook.class); + IMethodHook methodHookTwo = mock(IMethodHook.class); + IMethodHook methodHookThree = mock(IMethodHook.class); + + // the map for the constructor hooks + Map constructorHooks = new LinkedHashMap(); + + long sensorTypeIdOne = 7L; + long sensorTypeIdTwo = 13L; + long sensorTypeIdThree = 15L; + long exceptionSensorTypeId = 10L; + long methodId = 3L; + long constructorId = 1L; + + // the exception sensor type config + MethodSensorTypeConfig sensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(sensorTypeConfig.getName()).thenReturn("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"); + when(sensorTypeConfig.getId()).thenReturn(exceptionSensorTypeId); + when(registeredSensorConfig.getExceptionSensorTypeConfig()).thenReturn(sensorTypeConfig); + when(registeredConstructorSensorConfig.getExceptionSensorTypeConfig()).thenReturn(sensorTypeConfig); + + // the exception sensor hook + IMethodSensor exceptionSensor = mock(IMethodSensor.class); + when(sensorTypeConfig.getSensorType()).thenReturn(exceptionSensor); + ExceptionSensorHook exceptionHook = mock(ExceptionSensorHook.class); + when(exceptionSensor.getHook()).thenReturn(exceptionHook); + + // putting the hooks into the maps + constructorHooks.put(exceptionSensorTypeId, exceptionHook); + methodHooks.put(sensorTypeIdOne, methodHookOne); + methodHooks.put(sensorTypeIdTwo, methodHookTwo); + methodHooks.put(sensorTypeIdThree, methodHookThree); + reverseMethodHooks.put(sensorTypeIdThree, methodHookThree); + reverseMethodHooks.put(sensorTypeIdTwo, methodHookTwo); + reverseMethodHooks.put(sensorTypeIdOne, methodHookOne); + + when(registeredSensorConfig.getReverseMethodHooks()).thenReturn(reverseMethodHooks); + when(registeredSensorConfig.getMethodHooks()).thenReturn(methodHooks); + when(registeredConstructorSensorConfig.getReverseMethodHooks()).thenReturn(constructorHooks); + when(registeredConstructorSensorConfig.getMethodHooks()).thenReturn(constructorHooks); + + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object returnValue = mock(Object.class); + Object exceptionObject = mock(MyTestException.class); + + hookDispatcher.addMethodMapping(methodId, registeredSensorConfig); + hookDispatcher.addConstructorMapping(constructorId, registeredConstructorSensorConfig); + + hookDispatcher.dispatchMethodBeforeBody(methodId, object, parameters); + verify(registeredSensorConfig, times(1)).startsInvocationSequence(); + verify(registeredSensorConfig, times(1)).getReverseMethodHooks(); + InOrder inOrder = inOrder(methodHookOne, methodHookTwo, methodHookThree); + inOrder.verify(methodHookThree, times(1)).beforeBody(methodId, sensorTypeIdThree, object, parameters, registeredSensorConfig); + inOrder.verify(methodHookTwo, times(1)).beforeBody(methodId, sensorTypeIdTwo, object, parameters, registeredSensorConfig); + inOrder.verify(methodHookOne, times(1)).beforeBody(methodId, sensorTypeIdOne, object, parameters, registeredSensorConfig); + + hookDispatcher.dispatchConstructorBeforeBody(constructorId, parameters); + verify(exceptionHook, times(1)).beforeConstructor(constructorId, exceptionSensorTypeId, parameters, registeredConstructorSensorConfig); + + // first method of exception sensor + hookDispatcher.dispatchConstructorAfterBody(constructorId, exceptionObject, parameters); + verify(exceptionHook, times(1)).afterConstructor(coreService, constructorId, exceptionSensorTypeId, exceptionObject, parameters, registeredConstructorSensorConfig); + + // second method of exception sensor + hookDispatcher.dispatchOnThrowInBody(methodId, object, parameters, exceptionObject); + verify(registeredSensorConfig, times(2)).getExceptionSensorTypeConfig(); + verify(exceptionHook, times(1)).dispatchOnThrowInBody(coreService, methodId, exceptionSensorTypeId, object, exceptionObject, parameters, registeredSensorConfig); + + hookDispatcher.dispatchFirstMethodAfterBody(methodId, object, parameters, returnValue); + verify(registeredSensorConfig, times(1)).getMethodHooks(); + inOrder = inOrder(methodHookOne, methodHookTwo, methodHookThree); + inOrder.verify(methodHookOne, times(1)).firstAfterBody(methodId, sensorTypeIdOne, object, parameters, returnValue, registeredSensorConfig); + inOrder.verify(methodHookTwo, times(1)).firstAfterBody(methodId, sensorTypeIdTwo, object, parameters, returnValue, registeredSensorConfig); + inOrder.verify(methodHookThree, times(1)).firstAfterBody(methodId, sensorTypeIdThree, object, parameters, returnValue, registeredSensorConfig); + + hookDispatcher.dispatchSecondMethodAfterBody(methodId, object, parameters, returnValue); + verify(registeredSensorConfig, times(2)).startsInvocationSequence(); + verify(registeredSensorConfig, times(2)).getMethodHooks(); + inOrder = inOrder(methodHookOne, methodHookTwo, methodHookThree); + inOrder.verify(methodHookOne, times(1)).secondAfterBody(coreService, methodId, sensorTypeIdOne, object, parameters, returnValue, registeredSensorConfig); + inOrder.verify(methodHookTwo, times(1)).secondAfterBody(coreService, methodId, sensorTypeIdTwo, object, parameters, returnValue, registeredSensorConfig); + inOrder.verify(methodHookThree, times(1)).secondAfterBody(coreService, methodId, sensorTypeIdThree, object, parameters, returnValue, registeredSensorConfig); + + // third method of exception sensor + hookDispatcher.dispatchBeforeCatch(methodId, exceptionObject); + verify(registeredSensorConfig, times(4)).getExceptionSensorTypeConfig(); + verify(exceptionHook, times(1)).dispatchBeforeCatchBody(coreService, methodId, exceptionSensorTypeId, exceptionObject, registeredSensorConfig); + + verifyZeroInteractions(object, coreService, returnValue); + verifyNoMoreInteractions(methodHookOne, methodHookTwo, methodHookThree, exceptionHook, registeredSensorConfig); + } + + @Test + public void dispatchExceptionSensorWithOneMethodHookWithInvocationTrace() throws Exception { + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + RegisteredSensorConfig registeredConstructorSensorConfig = mock(RegisteredSensorConfig.class); + + long methodSensorTypeId = 23L; + long exceptionSensorTypeId = 10L; + long invocSensorTypeId = 13L; + long methodId = 3L; + long methodIdTwo = 15L; + long constructorId = 7L; + + // the exception sensor type config + MethodSensorTypeConfig sensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(sensorTypeConfig.getName()).thenReturn("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"); + when(sensorTypeConfig.getId()).thenReturn(exceptionSensorTypeId); + when(registeredSensorConfig.getExceptionSensorTypeConfig()).thenReturn(sensorTypeConfig); + when(registeredConstructorSensorConfig.getExceptionSensorTypeConfig()).thenReturn(sensorTypeConfig); + + // the exception sensor hook + IMethodSensor exceptionSensor = mock(IMethodSensor.class); + when(sensorTypeConfig.getSensorType()).thenReturn(exceptionSensor); + ExceptionSensorHook exceptionHook = mock(ExceptionSensorHook.class); + when(exceptionSensor.getHook()).thenReturn(exceptionHook); + + // the invocation sequence sensor type config + MethodSensorTypeConfig invocSensorType = mock(MethodSensorTypeConfig.class); + + IMethodSensor methodSensor = mock(IMethodSensor.class); + when(invocSensorType.getSensorType()).thenReturn(methodSensor); + InvocationSequenceHook invocHook = mock(InvocationSequenceHook.class); + when(methodSensor.getHook()).thenReturn(invocHook); + + // the map for the method hooks + Map methodHooks = new LinkedHashMap(); + IMethodHook methodHook = mock(IMethodHook.class); + // the map for the constructor hooks + Map constructorHooks = new LinkedHashMap(); + methodHooks.put(methodSensorTypeId, methodHook); + methodHooks.put(invocSensorTypeId, invocHook); + constructorHooks.put(exceptionSensorTypeId, exceptionHook); + when(registeredSensorConfig.getReverseMethodHooks()).thenReturn(methodHooks); + when(registeredSensorConfig.getMethodHooks()).thenReturn(methodHooks); + when(registeredConstructorSensorConfig.getReverseMethodHooks()).thenReturn(constructorHooks); + when(registeredConstructorSensorConfig.getMethodHooks()).thenReturn(constructorHooks); + when(registeredSensorConfig.startsInvocationSequence()).thenReturn(true); + when(registeredSensorConfig.getInvocationSequenceSensorTypeConfig()).thenReturn(invocSensorType); + + // second method + RegisteredSensorConfig registeredSensorConfigTwo = mock(RegisteredSensorConfig.class); + Map methodHooksTwo = new LinkedHashMap(); + methodHooksTwo.put(methodSensorTypeId, methodHook); + when(registeredSensorConfigTwo.getReverseMethodHooks()).thenReturn(methodHooksTwo); + when(registeredSensorConfigTwo.getMethodHooks()).thenReturn(methodHooksTwo); + + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object returnValue = mock(Object.class); + Object exceptionObject = mock(MyTestException.class); + + hookDispatcher.addMethodMapping(methodId, registeredSensorConfig); + hookDispatcher.addMethodMapping(methodIdTwo, registeredSensorConfigTwo); + hookDispatcher.addConstructorMapping(constructorId, registeredConstructorSensorConfig); + + // //////////////////////////////////////////////////////// + // FIRST METHOD DISPATCHER + + // dispatch the first method - before body + hookDispatcher.dispatchMethodBeforeBody(methodId, object, parameters); + verify(registeredSensorConfig, times(1)).startsInvocationSequence(); + verify(registeredSensorConfig, times(1)).getReverseMethodHooks(); + verify(registeredSensorConfig, times(1)).getInvocationSequenceSensorTypeConfig(); + verify(methodSensor, times(1)).getHook(); + verify(methodHook, times(1)).beforeBody(methodId, methodSensorTypeId, object, parameters, registeredSensorConfig); + verify(invocHook, times(1)).beforeBody(methodId, invocSensorTypeId, object, parameters, registeredSensorConfig); + verify(invocSensorType, times(1)).getSensorType(); + + // //////////////////////////////////////////////////////// + // SECOND METHOD DISPATCHER + + // dispatch the second method - before body + hookDispatcher.dispatchMethodBeforeBody(methodIdTwo, object, parameters); + verify(registeredSensorConfig, times(1)).startsInvocationSequence(); + verify(registeredSensorConfig, times(1)).getReverseMethodHooks(); + verify(methodSensor, times(1)).getHook(); + verify(methodHook, times(1)).beforeBody(methodIdTwo, methodSensorTypeId, object, parameters, registeredSensorConfigTwo); + verify(invocHook, times(1)).beforeBody(eq(methodIdTwo), anyLong(), eq(object), eq(parameters), eq(registeredSensorConfigTwo)); + verify(invocSensorType, times(1)).getSensorType(); + + hookDispatcher.dispatchConstructorBeforeBody(constructorId, parameters); + verify(exceptionHook, times(1)).beforeConstructor(constructorId, exceptionSensorTypeId, parameters, registeredConstructorSensorConfig); + + // ///////////////////////////////////////////////////////// + // ///////////// EXCEPTION SENSOR STARTS HERE + + // first method of exception sensor + hookDispatcher.dispatchConstructorAfterBody(constructorId, exceptionObject, parameters); + verify(exceptionHook, times(1)).afterConstructor(invocHook, constructorId, exceptionSensorTypeId, exceptionObject, parameters, registeredConstructorSensorConfig); + + // second method of exception sensor + hookDispatcher.dispatchOnThrowInBody(methodId, object, parameters, exceptionObject); + verify(registeredSensorConfig, times(2)).getExceptionSensorTypeConfig(); + verify(exceptionHook, times(1)).dispatchOnThrowInBody(invocHook, methodId, exceptionSensorTypeId, object, exceptionObject, parameters, registeredSensorConfig); + // ///////////// EXCEPTION SENSOR SECOND METHOD ENDS HERE + // ///////////////////////////////////////////////////////// + + // dispatch the second method - first after body + hookDispatcher.dispatchFirstMethodAfterBody(methodIdTwo, object, parameters, returnValue); + verify(registeredSensorConfigTwo, times(1)).getMethodHooks(); + verify(methodHook, times(1)).firstAfterBody(methodIdTwo, methodSensorTypeId, object, parameters, returnValue, registeredSensorConfigTwo); + + // dispatch the second method - second after body + hookDispatcher.dispatchSecondMethodAfterBody(methodIdTwo, object, parameters, returnValue); + verify(registeredSensorConfigTwo, times(2)).startsInvocationSequence(); + verify(registeredSensorConfigTwo, times(2)).getMethodHooks(); + verify(methodHook, times(1)).secondAfterBody(invocHook, methodIdTwo, methodSensorTypeId, object, parameters, returnValue, registeredSensorConfigTwo); + verify(invocHook, times(1)).secondAfterBody(eq(coreService), eq(methodIdTwo), anyLong(), eq(object), eq(parameters), eq(returnValue), eq(registeredSensorConfigTwo)); + // END SECOND METHOD DISPATCHER + // //////////////////////////////////////////////////////// + + // third method of exception sensor + hookDispatcher.dispatchBeforeCatch(methodId, exceptionObject); + verify(registeredSensorConfig, times(4)).getExceptionSensorTypeConfig(); + verify(exceptionHook, times(1)).dispatchBeforeCatchBody(invocHook, methodId, exceptionSensorTypeId, exceptionObject, registeredSensorConfig); + + // dispatch the first method - first after body + hookDispatcher.dispatchFirstMethodAfterBody(methodId, object, parameters, returnValue); + verify(registeredSensorConfig, times(1)).getMethodHooks(); + verify(methodHook, times(1)).firstAfterBody(methodId, methodSensorTypeId, object, parameters, returnValue, registeredSensorConfig); + verify(invocHook, times(1)).firstAfterBody(methodId, invocSensorTypeId, object, parameters, returnValue, registeredSensorConfig); + + // dispatch the first method - second after body + hookDispatcher.dispatchSecondMethodAfterBody(methodId, object, parameters, returnValue); + verify(registeredSensorConfig, times(2)).startsInvocationSequence(); + verify(registeredSensorConfig, times(2)).getMethodHooks(); + verify(methodHook, times(1)).secondAfterBody(invocHook, methodId, methodSensorTypeId, object, parameters, returnValue, registeredSensorConfig); + verify(invocHook, times(1)).secondAfterBody(coreService, methodId, invocSensorTypeId, object, parameters, returnValue, registeredSensorConfig); + + // END FIRST METHOD DISPATCHER + // //////////////////////////////////////////////////////// + + verifyZeroInteractions(object, coreService, returnValue); + verifyNoMoreInteractions(methodHook, exceptionHook, registeredSensorConfig); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/hooking/impl/HookInstrumenterTest.java b/Agent/test/info/novatec/inspectit/agent/hooking/impl/HookInstrumenterTest.java new file mode 100644 index 000000000..2efec4479 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/hooking/impl/HookInstrumenterTest.java @@ -0,0 +1,1392 @@ +package info.novatec.inspectit.agent.hooking.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.IAgent; +import info.novatec.inspectit.agent.analyzer.classes.ExceptionTestClass; +import info.novatec.inspectit.agent.analyzer.classes.ExceptionalTestClass; +import info.novatec.inspectit.agent.analyzer.classes.ITest; +import info.novatec.inspectit.agent.analyzer.classes.MyTestException; +import info.novatec.inspectit.agent.analyzer.classes.TestClass; +import info.novatec.inspectit.agent.analyzer.classes.TestClassLoader; +import info.novatec.inspectit.agent.config.IConfigurationStorage; +import info.novatec.inspectit.agent.config.impl.MethodSensorTypeConfig; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.hooking.IHookDispatcher; +import info.novatec.inspectit.agent.hooking.IHookDispatcherMapper; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.Loader; +import javassist.LoaderClassPath; +import javassist.NotFoundException; + +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class HookInstrumenterTest extends AbstractLogSupport { + + private interface ITestHookDispatcher extends IHookDispatcher, IHookDispatcherMapper { + } + + @Mock + private static ITestHookDispatcher hookDispatcher; + + @Mock + private IIdManager idManager; + + @Mock + private IConfigurationStorage configurationStorage; + + @Mock + private static IAgent agent; + + private HookInstrumenter hookInstrumenter; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { + hookInstrumenter = new HookInstrumenter(hookDispatcher, idManager, configurationStorage); + hookInstrumenter.log = LoggerFactory.getLogger(HookInstrumenter.class); + Field field = hookInstrumenter.getClass().getDeclaredField("hookDispatcherTarget"); + field.setAccessible(true); + field.set(hookInstrumenter, "info.novatec.inspectit.agent.hooking.impl.HookInstrumenterTest#getHookDispatcher()"); + + field = hookInstrumenter.getClass().getDeclaredField("agentTarget"); + field.setAccessible(true); + field.set(hookInstrumenter, "info.novatec.inspectit.agent.hooking.impl.HookInstrumenterTest#getAgent()"); + } + + public static ITestHookDispatcher getHookDispatcher() { + return hookDispatcher; + } + + public static IAgent getAgent() { + return agent; + } + + private Loader createLoader() { + ClassPool classPool = ClassPool.getDefault(); + Loader loader = new Loader(this.getClass().getClassLoader(), classPool); + loader.delegateLoadingOf(HookInstrumenterTest.class.getName()); + loader.delegateLoadingOf(IHookDispatcher.class.getName()); + loader.delegateLoadingOf(IAgent.class.getName()); + return loader; + } + + private CtMethod getCtMethod(Loader loader, String className, String methodName) throws NotFoundException { + ClassPool classPool = new ClassPool(); + classPool.insertClassPath(new LoaderClassPath(loader)); + return classPool.getMethod(className, methodName); + } + + private CtClass getCtClass(Loader loader, String className) throws NotFoundException { + ClassPool classPool = new ClassPool(); + classPool.insertClassPath(new LoaderClassPath(loader)); + return classPool.get(className); + } + + /** + * As it is not possible to modify the java byte code on the fly, we have to get it from the + * class pool. + */ + private Object createInstance(Loader loader, CtMethod ctMethod) throws Exception { + Class clazz = ctMethod.getDeclaringClass().toClass(loader, null); + return clazz.newInstance(); + } + + private Object callMethod(Object object, String methodName, Object[] parameters) throws Exception { + if (null == parameters) { + parameters = new Object[0]; + } + Class clazz = object.getClass(); + Class[] parameterClasses = null; + parameterClasses = new Class[parameters.length]; + for (int i = 0; i < parameterClasses.length; i++) { + String parameter = (String) parameters[i]; + if ("int".equals(parameter)) { + parameterClasses[i] = Integer.TYPE; + parameters[i] = 3; + } else if ("boolean".equals(parameter)) { + parameterClasses[i] = Boolean.TYPE; + parameters[i] = false; + } else { + parameterClasses[i] = Class.forName(parameter); + } + } + Method method = clazz.getDeclaredMethod(methodName, parameterClasses); + method.setAccessible(true); + return method.invoke(object, parameters); + } + + @Test + public void methodHookNoStatic() throws Exception { + String methodName = "stringNullParameter"; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 3L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + verify(idManager).registerMethod(registeredSensorConfig); + verify(hookDispatcher).addMethodMapping(methodId, registeredSensorConfig); + verifyNoMoreInteractions(idManager, hookDispatcher); + + // now call this method + Object testClass = this.createInstance(loader, ctMethod); + // call this method via reflection as we would get a class cast + // exception by casting to the concrete class. + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], "stringNullParameter"); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], "stringNullParameter"); + verifyNoMoreInteractions(idManager, hookDispatcher); + } + + @Test + public void methodHookStatic() throws Exception { + String methodName = "voidNullParameterStatic"; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 7L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + verify(idManager).registerMethod(registeredSensorConfig); + verify(hookDispatcher).addMethodMapping(methodId, registeredSensorConfig); + verifyNoMoreInteractions(idManager, hookDispatcher); + + // now call this method + Object testClass = this.createInstance(loader, ctMethod); + // call this method via reflection as we would get a class cast + // exception by casting to the concrete class. + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, null, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, null, new Object[0], null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, null, new Object[0], null); + verifyNoMoreInteractions(idManager, hookDispatcher); + } + + @Test + public void testSensorTypeRegistrations() throws NotFoundException, HookException { + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + List sensorTypeConfigs = new ArrayList(); + MethodSensorTypeConfig sensorTypeConfig = mock(MethodSensorTypeConfig.class); + long sensorTypeId = 11L; + when(sensorTypeConfig.getId()).thenReturn(sensorTypeId); + sensorTypeConfigs.add(sensorTypeConfig); + when(registeredSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + CtMethod ctMethod = ClassPool.getDefault().getMethod(TestClass.class.getName(), "voidNullParameterStatic"); + long methodId = 7L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + verify(idManager).registerMethod(registeredSensorConfig); + verify(idManager).addSensorTypeToMethod(sensorTypeId, methodId); + verify(hookDispatcher).addMethodMapping(methodId, registeredSensorConfig); + verifyNoMoreInteractions(idManager, hookDispatcher); + } + + @Test(expectedExceptions = { HookException.class }) + public void instrumentInterface() throws NotFoundException, HookException { + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + CtMethod ctMethod = ClassPool.getDefault().getMethod(ITest.class.getName(), "voidNullParameter"); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + } + + @Test + public void repeatInstrumentation() throws NotFoundException, HookException { + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + CtMethod ctMethod = ClassPool.getDefault().getMethod(TestClass.class.getName(), "stringNullParameter"); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + } + + // above tests were used to find out if the hookinstrumenter seems to work, + // the lower ones are needed to verify if all methods are instrumented + // correctly. + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void classLoaderClassDelegated() throws Exception { + String methodName = "loadClass"; + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClassLoader.class.getName(), methodName); + + hookInstrumenter.addClassLoaderDelegationHook(ctMethod); + + Class loadedByUs = String.class; + String className = "java.lang.String"; + Object[] parameters = new Object[] { className }; + when(agent.loadClass(parameters)).thenReturn(loadedByUs); + + // we expect that agent delivers loadedByUs, thus also the class loader + + Object testClass = this.createInstance(loader, ctMethod); + Object result = this.callMethod(testClass, methodName, parameters); + + verify(agent, times(1)).loadClass(new Object[] { className }); + assertThat(result, is(instanceOf(Class.class))); + assertThat((Class) result, is(equalTo(loadedByUs))); + } + + @SuppressWarnings("rawtypes") + @Test + public void classLoaderClassNotDelegated() throws Exception { + String methodName = "loadClass"; + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClassLoader.class.getName(), methodName); + + hookInstrumenter.addClassLoaderDelegationHook(ctMethod); + + String className = "java.lang.String"; + Object[] parameters = new Object[] { className }; + when(agent.loadClass(parameters)).thenReturn(null); + + // we expect that agent delivers null, thus the class loader returns his own class + + Object testClass = this.createInstance(loader, ctMethod); + Object result = this.callMethod(testClass, methodName, parameters); + + verify(agent, times(1)).loadClass(new Object[] { className }); + Class expected = testClass.getClass(); + assertThat((Class) result, is(equalTo(expected))); + } + + @Test + public void voidNullParameter() throws Exception { + String methodName = "voidNullParameter"; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], null); + } + + @Test + public void stringNullParameter() throws Exception { + String methodName = "stringNullParameter"; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], "stringNullParameter"); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], "stringNullParameter"); + } + + @Test + public void intNullParameter() throws Exception { + String methodName = "intNullParameter"; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], 3); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], 3); + } + + @Test + public void doubleNullParameter() throws Exception { + String methodName = "doubleNullParameter"; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], 5.3D); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], 5.3D); + } + + @Test + public void floatNullParameter() throws Exception { + String methodName = "floatNullParameter"; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], Float.MAX_VALUE); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], Float.MAX_VALUE); + } + + @Test + public void byteNullParameter() throws Exception { + String methodName = "byteNullParameter"; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], (byte) 127); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], (byte) 127); + } + + @Test + public void shortNullParameter() throws Exception { + String methodName = "shortNullParameter"; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], (short) 16345); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], (short) 16345); + } + + @Test + public void booleanNullParameter() throws Exception { + String methodName = "booleanNullParameter"; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], false); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], false); + } + + @Test + public void charNullParameter() throws Exception { + String methodName = "charNullParameter"; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], '\u1234'); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], '\u1234'); + } + + @Test + public void voidNullParameterStatic() throws Exception { + String methodName = "voidNullParameterStatic"; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, null, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, null, new Object[0], null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, null, new Object[0], null); + } + + @Test + public void stringNullParameterStatic() throws Exception { + String methodName = "stringNullParameterStatic"; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, null, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, null, new Object[0], "stringNullParameterStatic"); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, null, new Object[0], "stringNullParameterStatic"); + } + + @Test + public void voidOneParameter() throws Exception { + String methodName = "voidOneParameter"; + Object[] parameters = { "java.lang.String" }; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, parameters); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, parameters); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, parameters, null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, parameters, null); + } + + @Test + public void stringOneParameter() throws Exception { + String methodName = "stringOneParameter"; + Object[] parameters = { "java.lang.String" }; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, parameters); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, parameters); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, parameters, "stringOneParameter"); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, parameters, "stringOneParameter"); + } + + @Test + public void voidTwoParameters() throws Exception { + String methodName = "voidTwoParameters"; + Object[] parameters = { "java.lang.String", "java.lang.Object" }; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, parameters); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, parameters); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, parameters, null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, parameters, null); + } + + @Test + public void mixedTwoParameters() throws Exception { + String methodName = "mixedTwoParameters"; + Object[] parameters = { "int", "boolean" }; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, parameters); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, parameters); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, parameters, null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, parameters, null); + } + + @Test + public void intArrayNullParameter() throws Exception { + String methodName = "intArrayNullParameter"; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], new int[] { 1, 2, 3 }); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], new int[] { 1, 2, 3 }); + } + + @Test + public void stringArrayNullParameter() throws Exception { + String methodName = "stringArrayNullParameter"; + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtMethod ctMethod = this.getCtMethod(loader, TestClass.class.getName(), methodName); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], new String[] { "test123", "bla" }); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], new String[] { "test123", "bla" }); + } + + @Test + public void constructorNullParameter() throws Exception { + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtClass ctClass = this.getCtClass(loader, TestClass.class.getName()); + CtConstructor ctConstructor = ctClass.getConstructor("()V"); + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addConstructorHook(ctConstructor, registeredSensorConfig); + + Class clazz = ctClass.toClass(loader, null); + Object testClass = clazz.newInstance(); + + verify(hookDispatcher).dispatchConstructorBeforeBody(methodId, new Object[0]); + verify(hookDispatcher).dispatchConstructorAfterBody(methodId, testClass, new Object[0]); + } + + @Test + public void constructorStringOneParameter() throws Exception { + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtClass ctClass = this.getCtClass(loader, TestClass.class.getName()); + CtConstructor ctConstructor = ctClass.getConstructor("(Ljava/lang/String;)V"); + Object[] parameters = { "java.lang.String" }; + long methodId = 9L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + + hookInstrumenter.addConstructorHook(ctConstructor, registeredSensorConfig); + + Class clazz = ctClass.toClass(loader, null); + Constructor constructor = clazz.getConstructor(new Class[] { String.class }); + Object testClass = constructor.newInstance(parameters); + + verify(hookDispatcher).dispatchConstructorBeforeBody(methodId, parameters); + verify(hookDispatcher).dispatchConstructorAfterBody(methodId, testClass, parameters); + } + + @Test + public void nestedConstructorBooleanOneParameter() throws Exception { + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + Loader loader = this.createLoader(); + CtClass ctClass = this.getCtClass(loader, TestClass.class.getName()); + CtConstructor ctConstructor = ctClass.getConstructor("(Z)V"); + Object[] parameters = { Boolean.TRUE }; + CtConstructor nestedCtConstructor = ctClass.getConstructor("(Ljava/lang/String;)V"); + Object[] nestedParameters = { "delegate" }; + long methodId = 9L; + long nestedMethodId = 13L; + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId).thenReturn(nestedMethodId); + + hookInstrumenter.addConstructorHook(ctConstructor, registeredSensorConfig); + hookInstrumenter.addConstructorHook(nestedCtConstructor, registeredSensorConfig); + + Class clazz = ctClass.toClass(loader, null); + Constructor constructor = clazz.getConstructor(new Class[] { Boolean.TYPE }); + Object testClass = constructor.newInstance(parameters); + + verify(hookDispatcher).dispatchConstructorBeforeBody(nestedMethodId, nestedParameters); + verify(hookDispatcher).dispatchConstructorAfterBody(nestedMethodId, testClass, nestedParameters); + verify(hookDispatcher).dispatchConstructorBeforeBody(methodId, parameters); + verify(hookDispatcher).dispatchConstructorAfterBody(methodId, testClass, parameters); + } + + @Test + public void constructorsOfThrowableAreInstrumented() throws Exception { + Loader loader = this.createLoader(); + ClassPool classPool = new ClassPool(); + classPool.insertClassPath(new LoaderClassPath(loader)); + loader.setClassPool(classPool); + CtClass exceptionClazz = classPool.get(MyTestException.class.getName()); + + long constructorId = 5L; + long sensorTypeId = 10L; + + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + when(registeredSensorConfig.isConstructor()).thenReturn(true); + when(registeredSensorConfig.getTargetClassName()).thenReturn(exceptionClazz.getSimpleName()); + when(registeredSensorConfig.getTargetMethodName()).thenReturn(exceptionClazz.getSimpleName()); + when(registeredSensorConfig.getModifiers()).thenReturn(exceptionClazz.getModifiers()); + + MethodSensorTypeConfig sensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(sensorTypeConfig.getName()).thenReturn("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"); + when(sensorTypeConfig.getId()).thenReturn(sensorTypeId); + when(registeredSensorConfig.isConstructor()).thenReturn(true); + when(registeredSensorConfig.getExceptionSensorTypeConfig()).thenReturn(sensorTypeConfig); + when(registeredSensorConfig.getId()).thenReturn(sensorTypeId); + + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(constructorId); + List sensorTypeConfigs = new ArrayList(); + sensorTypeConfigs.add(sensorTypeConfig); + when(registeredSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + + // instrumenting and verifying that constructors are instrumented + CtConstructor[] constructors = exceptionClazz.getConstructors(); + for (int i = 0; i < constructors.length; i++) { + hookInstrumenter.addConstructorHook(constructors[i], registeredSensorConfig); + } + verify(idManager, times(3)).addSensorTypeToMethod(sensorTypeId, constructorId); + verify(hookDispatcher, times(3)).addConstructorMapping(constructorId, registeredSensorConfig); + } + + @Test + public void exceptionObjectIsCreated() throws Exception { + String methodName = "createsExceptionObject"; + Loader loader = this.createLoader(); + ClassPool classPool = new ClassPool(); + classPool.insertClassPath(new LoaderClassPath(loader)); + loader.setClassPool(classPool); + CtMethod ctMethod = classPool.getMethod(ExceptionTestClass.class.getName(), methodName); + CtClass exceptionClazz = classPool.get(MyTestException.class.getName()); + + long sensorTypeId = 10L; + long constructorId = 5L; + + MyTestException exceptionObject = MyTestException.class.newInstance(); + System.out.println(exceptionObject.getClass().getClassLoader()); + + // initializing / mocking the RegisteredSensorConfig and some needed + // variables + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + when(registeredSensorConfig.getId()).thenReturn(sensorTypeId); + when(configurationStorage.isExceptionSensorActivated()).thenReturn(true); + when(registeredSensorConfig.isConstructor()).thenReturn(true); + when(registeredSensorConfig.getTargetClassName()).thenReturn(exceptionClazz.getSimpleName()); + when(registeredSensorConfig.getTargetMethodName()).thenReturn(exceptionClazz.getSimpleName()); + when(registeredSensorConfig.getModifiers()).thenReturn(exceptionClazz.getModifiers()); + MethodSensorTypeConfig sensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(sensorTypeConfig.getName()).thenReturn("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"); + when(sensorTypeConfig.getId()).thenReturn(sensorTypeId); + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(constructorId); + List sensorTypeConfigs = new ArrayList(); + sensorTypeConfigs.add(sensorTypeConfig); + when(registeredSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + + // instrumenting the constructor + CtConstructor[] constructors = exceptionClazz.getConstructors(); + for (int i = 0; i < constructors.length; i++) { + hookInstrumenter.addConstructorHook(constructors[i], registeredSensorConfig); + } + verify(idManager, times(3)).addSensorTypeToMethod(sensorTypeId, constructorId); + verify(hookDispatcher, times(3)).addConstructorMapping(constructorId, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchConstructorBeforeBody(eq(constructorId), (Object[]) anyObject()); + verify(hookDispatcher).dispatchConstructorAfterBody(eq(constructorId), argThat(new MyTestExceptionVerifier(exceptionObject)), (Object[]) anyObject()); + verifyNoMoreInteractions(hookDispatcher); + } + + @Test + public void exceptionThrowerIsInstrumented() throws Exception { + String methodName = "throwsAndHandlesException"; + Loader loader = this.createLoader(); + ClassPool classPool = new ClassPool(); + classPool.insertClassPath(new LoaderClassPath(loader)); + loader.setClassPool(classPool); + CtMethod ctMethod = classPool.getMethod(ExceptionTestClass.class.getName(), methodName); + + long methodId = 9L; + long sensorTypeId = 10L; + + MyTestException exceptionObject = MyTestException.class.newInstance(); + + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + when(registeredSensorConfig.getId()).thenReturn(sensorTypeId); + when(configurationStorage.isExceptionSensorActivated()).thenReturn(true); + when(configurationStorage.isEnhancedExceptionSensorActivated()).thenReturn(true); + + MethodSensorTypeConfig sensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(sensorTypeConfig.getName()).thenReturn("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"); + when(sensorTypeConfig.getId()).thenReturn(sensorTypeId); + List sensorTypeConfigs = new ArrayList(); + sensorTypeConfigs.add(sensorTypeConfig); + when(registeredSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + + verify(idManager, times(1)).addSensorTypeToMethod(sensorTypeId, methodId); + verify(hookDispatcher, times(1)).addMethodMapping(methodId, registeredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], null); + verify(hookDispatcher).dispatchBeforeCatch(eq(methodId), argThat(new MyTestExceptionVerifier(exceptionObject))); + verifyNoMoreInteractions(hookDispatcher); + } + + @Test + public void exceptionThrowerIsInstrumentedWhenConstructor() throws Exception { + Loader loader = this.createLoader(); + ClassPool classPool = new ClassPool(); + classPool.insertClassPath(new LoaderClassPath(loader)); + loader.setClassPool(classPool); + String methodName = "constructorThrowsAnException"; + CtMethod ctMethod = classPool.getMethod(ExceptionTestClass.class.getName(), methodName); + + CtClass ctClass = this.getCtClass(loader, ExceptionalTestClass.class.getName()); + CtConstructor ctConstructor = ctClass.getConstructor("(Ljava/lang/String;)V"); + + long sensorTypeId = 10L; + long constructorId = 11L; + long methodId = 9L; + + MyTestException exceptionObject = MyTestException.class.newInstance(); + + // sensor type settings + MethodSensorTypeConfig sensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(sensorTypeConfig.getName()).thenReturn("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"); + when(sensorTypeConfig.getId()).thenReturn(sensorTypeId); + List sensorTypeConfigs = new ArrayList(); + sensorTypeConfigs.add(sensorTypeConfig); + + // method settings + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + when(registeredSensorConfig.getId()).thenReturn(sensorTypeId); + when(configurationStorage.isExceptionSensorActivated()).thenReturn(true); + when(configurationStorage.isEnhancedExceptionSensorActivated()).thenReturn(true); + when(registeredSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + verify(idManager, times(1)).addSensorTypeToMethod(sensorTypeId, methodId); + verify(hookDispatcher, times(1)).addMethodMapping(methodId, registeredSensorConfig); + + // constructor settings + RegisteredSensorConfig registeredConstructorSensorConfig = mock(RegisteredSensorConfig.class); + when(idManager.registerMethod(registeredConstructorSensorConfig)).thenReturn(constructorId); + when(registeredConstructorSensorConfig.isConstructor()).thenReturn(true); + when(registeredConstructorSensorConfig.getId()).thenReturn(sensorTypeId); + when(registeredConstructorSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + hookInstrumenter.addConstructorHook(ctConstructor, registeredConstructorSensorConfig); + verify(idManager, times(1)).addSensorTypeToMethod(sensorTypeId, constructorId); + verify(hookDispatcher, times(1)).addConstructorMapping(constructorId, registeredConstructorSensorConfig); + + Class clazz = ctClass.toClass(loader, null); + Object testConstructor = clazz.newInstance(); + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], null); + verify(hookDispatcher).dispatchConstructorOnThrowInBody(eq(constructorId), argThat(new ObjectVerifier(testConstructor)), (Object[]) anyObject(), + argThat(new MyTestExceptionVerifier(exceptionObject))); + verify(hookDispatcher).dispatchConstructorBeforeBody(eq(constructorId), (Object[]) anyObject()); + verify(hookDispatcher).dispatchConstructorAfterBody(eq(constructorId), argThat(new ObjectVerifier(testConstructor)), (Object[]) anyObject()); + verify(hookDispatcher).dispatchBeforeCatch(eq(methodId), argThat(new MyTestExceptionVerifier(exceptionObject))); + + verifyNoMoreInteractions(hookDispatcher); + } + + @Test + public void exceptionThrowerAndHandlerAreInstrumented() throws Exception { + String methodName = "callsMethodWithException"; + String innerMethodName = "throwsAnException"; + Loader loader = this.createLoader(); + ClassPool classPool = new ClassPool(); + classPool.insertClassPath(new LoaderClassPath(loader)); + loader.setClassPool(classPool); + CtMethod ctMethod = classPool.getMethod(ExceptionTestClass.class.getName(), methodName); + CtMethod innerCtMethod = classPool.getMethod(ExceptionTestClass.class.getName(), innerMethodName); + + long methodId = 9L; + long innerMethodId = 11L; + long sensorTypeId = 10L; + + MyTestException exceptionObject = MyTestException.class.newInstance(); + + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + RegisteredSensorConfig innerRegisteredSensorConfig = mock(RegisteredSensorConfig.class); + when(registeredSensorConfig.getId()).thenReturn(sensorTypeId); + when(innerRegisteredSensorConfig.getId()).thenReturn(sensorTypeId); + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + when(idManager.registerMethod(innerRegisteredSensorConfig)).thenReturn(innerMethodId); + when(configurationStorage.isExceptionSensorActivated()).thenReturn(true); + when(configurationStorage.isEnhancedExceptionSensorActivated()).thenReturn(true); + + MethodSensorTypeConfig sensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(sensorTypeConfig.getName()).thenReturn("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"); + when(sensorTypeConfig.getId()).thenReturn(sensorTypeId); + List sensorTypeConfigs = new ArrayList(); + sensorTypeConfigs.add(sensorTypeConfig); + when(registeredSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + when(innerRegisteredSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + + // instrumenting the first method + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + verify(idManager, times(1)).addSensorTypeToMethod(sensorTypeId, methodId); + verify(hookDispatcher, times(1)).addMethodMapping(methodId, registeredSensorConfig); + + // instrumenting the called inner method + hookInstrumenter.addMethodHook(innerCtMethod, innerRegisteredSensorConfig); + verify(idManager, times(1)).addSensorTypeToMethod(sensorTypeId, innerMethodId); + verify(hookDispatcher, times(1)).addMethodMapping(innerMethodId, innerRegisteredSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + // first method + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], null); + + // inner method + verify(hookDispatcher).dispatchMethodBeforeBody(innerMethodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(innerMethodId, testClass, new Object[0], null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(innerMethodId, testClass, new Object[0], null); + + verify(hookDispatcher).dispatchOnThrowInBody(eq(innerMethodId), eq(testClass), (Object[]) anyObject(), argThat(new MyTestExceptionVerifier(exceptionObject))); + verify(hookDispatcher).dispatchBeforeCatch(eq(methodId), argThat(new MyTestExceptionVerifier(exceptionObject))); + verifyNoMoreInteractions(hookDispatcher); + } + + @Test + public void everythingInstrumentedByExceptionSensor() throws Exception { + String methodName = "callsMethodWithException"; + String innerMethodName = "throwsAnException"; + // we need to create an new loader with a new classPool + Loader loader = this.createLoader(); + ClassPool classPool = new ClassPool(); + classPool.insertClassPath(new LoaderClassPath(loader)); + loader.setClassPool(classPool); + CtMethod ctMethod = classPool.getMethod(ExceptionTestClass.class.getName(), methodName); + CtMethod innerCtMethod = classPool.getMethod(ExceptionTestClass.class.getName(), innerMethodName); + CtClass exceptionClazz = classPool.get(MyTestException.class.getName()); + + long methodId = 9L; + long innerMethodId = 11L; + long sensorTypeId = 10L; + long constructorId = 5L; + + MyTestException exceptionObject = MyTestException.class.newInstance(); + + // initializing / mocking the RegisteredSensorConfig for the + // constructors and some needed variables + RegisteredSensorConfig registeredConstructorSensorConfig = mock(RegisteredSensorConfig.class); + when(registeredConstructorSensorConfig.getId()).thenReturn(sensorTypeId); + when(registeredConstructorSensorConfig.isConstructor()).thenReturn(true); + when(registeredConstructorSensorConfig.getTargetClassName()).thenReturn(exceptionClazz.getSimpleName()); + when(registeredConstructorSensorConfig.getTargetMethodName()).thenReturn(exceptionClazz.getSimpleName()); + when(registeredConstructorSensorConfig.getModifiers()).thenReturn(exceptionClazz.getModifiers()); + when(idManager.registerMethod(registeredConstructorSensorConfig)).thenReturn(constructorId); + + // stuff for the methods + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + RegisteredSensorConfig innerRegisteredSensorConfig = mock(RegisteredSensorConfig.class); + when(registeredSensorConfig.getId()).thenReturn(sensorTypeId); + when(innerRegisteredSensorConfig.getId()).thenReturn(sensorTypeId); + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + when(idManager.registerMethod(innerRegisteredSensorConfig)).thenReturn(innerMethodId); + when(configurationStorage.isExceptionSensorActivated()).thenReturn(true); + when(configurationStorage.isEnhancedExceptionSensorActivated()).thenReturn(true); + + MethodSensorTypeConfig sensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(sensorTypeConfig.getName()).thenReturn("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"); + when(sensorTypeConfig.getId()).thenReturn(sensorTypeId); + List sensorTypeConfigs = new ArrayList(); + sensorTypeConfigs.add(sensorTypeConfig); + + when(registeredSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + when(innerRegisteredSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + when(registeredConstructorSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + + // instrumenting the first method + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + verify(idManager, times(1)).addSensorTypeToMethod(sensorTypeId, methodId); + verify(hookDispatcher, times(1)).addMethodMapping(methodId, registeredSensorConfig); + + // instrumenting the called inner method + hookInstrumenter.addMethodHook(innerCtMethod, innerRegisteredSensorConfig); + verify(idManager, times(1)).addSensorTypeToMethod(sensorTypeId, innerMethodId); + verify(hookDispatcher, times(1)).addMethodMapping(innerMethodId, innerRegisteredSensorConfig); + + // instrumenting the constructors + CtConstructor[] constructors = exceptionClazz.getConstructors(); + for (int i = 0; i < constructors.length; i++) { + hookInstrumenter.addConstructorHook(constructors[i], registeredConstructorSensorConfig); + } + + verify(idManager, times(3)).addSensorTypeToMethod(sensorTypeId, constructorId); + verify(hookDispatcher, times(3)).addConstructorMapping(constructorId, registeredConstructorSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + // first method + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], null); + + // inner method + verify(hookDispatcher).dispatchMethodBeforeBody(innerMethodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(innerMethodId, testClass, new Object[0], null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(innerMethodId, testClass, new Object[0], null); + + verify(hookDispatcher).dispatchConstructorBeforeBody(eq(constructorId), (Object[]) anyObject()); + verify(hookDispatcher).dispatchConstructorAfterBody(eq(constructorId), argThat(new MyTestExceptionVerifier(exceptionObject)), (Object[]) anyObject()); + verify(hookDispatcher).dispatchOnThrowInBody(eq(innerMethodId), eq(testClass), (Object[]) anyObject(), argThat(new MyTestExceptionVerifier(exceptionObject))); + verify(hookDispatcher).dispatchBeforeCatch(eq(methodId), argThat(new MyTestExceptionVerifier(exceptionObject))); + verifyNoMoreInteractions(hookDispatcher); + } + + @Test + public void tryCatchInstrumentedByExceptionSensorWithFinally() throws Exception { + String methodName = "callsMethodWithExceptionAndTryCatchFinally"; + String innerMethodName = "throwsAnException"; + // we need to create an new loader with a new classPool + Loader loader = this.createLoader(); + ClassPool classPool = new ClassPool(); + classPool.insertClassPath(new LoaderClassPath(loader)); + loader.setClassPool(classPool); + CtMethod ctMethod = classPool.getMethod(ExceptionTestClass.class.getName(), methodName); + CtMethod innerCtMethod = classPool.getMethod(ExceptionTestClass.class.getName(), innerMethodName); + CtClass exceptionClazz = classPool.get(MyTestException.class.getName()); + + long methodId = 9L; + long innerMethodId = 11L; + long sensorTypeId = 10L; + long constructorId = 5L; + + MyTestException exceptionObject = MyTestException.class.newInstance(); + + // initializing / mocking the RegisteredSensorConfig for the + // constructors and some needed variables + RegisteredSensorConfig registeredConstructorSensorConfig = mock(RegisteredSensorConfig.class); + when(registeredConstructorSensorConfig.getId()).thenReturn(sensorTypeId); + when(registeredConstructorSensorConfig.isConstructor()).thenReturn(true); + when(registeredConstructorSensorConfig.getTargetClassName()).thenReturn(exceptionClazz.getSimpleName()); + when(registeredConstructorSensorConfig.getTargetMethodName()).thenReturn(exceptionClazz.getSimpleName()); + when(registeredConstructorSensorConfig.getModifiers()).thenReturn(exceptionClazz.getModifiers()); + when(idManager.registerMethod(registeredConstructorSensorConfig)).thenReturn(constructorId); + + // stuff for the methods + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + RegisteredSensorConfig innerRegisteredSensorConfig = mock(RegisteredSensorConfig.class); + when(registeredSensorConfig.getId()).thenReturn(sensorTypeId); + when(innerRegisteredSensorConfig.getId()).thenReturn(sensorTypeId); + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + when(idManager.registerMethod(innerRegisteredSensorConfig)).thenReturn(innerMethodId); + when(configurationStorage.isExceptionSensorActivated()).thenReturn(true); + when(configurationStorage.isEnhancedExceptionSensorActivated()).thenReturn(true); + + MethodSensorTypeConfig sensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(sensorTypeConfig.getName()).thenReturn("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"); + when(sensorTypeConfig.getId()).thenReturn(sensorTypeId); + List sensorTypeConfigs = new ArrayList(); + sensorTypeConfigs.add(sensorTypeConfig); + + when(registeredSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + when(innerRegisteredSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + when(registeredConstructorSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + + // instrumenting the first method + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + verify(idManager, times(1)).addSensorTypeToMethod(sensorTypeId, methodId); + verify(hookDispatcher, times(1)).addMethodMapping(methodId, registeredSensorConfig); + + // instrumenting the called inner method + hookInstrumenter.addMethodHook(innerCtMethod, innerRegisteredSensorConfig); + verify(idManager, times(1)).addSensorTypeToMethod(sensorTypeId, innerMethodId); + verify(hookDispatcher, times(1)).addMethodMapping(innerMethodId, innerRegisteredSensorConfig); + + // instrumenting the constructors + CtConstructor[] constructors = exceptionClazz.getConstructors(); + for (int i = 0; i < constructors.length; i++) { + hookInstrumenter.addConstructorHook(constructors[i], registeredConstructorSensorConfig); + } + + verify(idManager, times(3)).addSensorTypeToMethod(sensorTypeId, constructorId); + verify(hookDispatcher, times(3)).addConstructorMapping(constructorId, registeredConstructorSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + // first method + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], null); + + // inner method + verify(hookDispatcher).dispatchMethodBeforeBody(innerMethodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(innerMethodId, testClass, new Object[0], null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(innerMethodId, testClass, new Object[0], null); + + verify(hookDispatcher).dispatchConstructorBeforeBody(eq(constructorId), (Object[]) anyObject()); + verify(hookDispatcher).dispatchConstructorAfterBody(eq(constructorId), argThat(new MyTestExceptionVerifier(exceptionObject)), (Object[]) anyObject()); + verify(hookDispatcher).dispatchOnThrowInBody(eq(innerMethodId), eq(testClass), (Object[]) anyObject(), argThat(new MyTestExceptionVerifier(exceptionObject))); + verify(hookDispatcher).dispatchBeforeCatch(eq(methodId), argThat(new MyTestExceptionVerifier(exceptionObject))); + verifyNoMoreInteractions(hookDispatcher); + } + + @Test + public void tryCatchNotInstrumentedBySimpleExceptionSensor() throws Exception { + String methodName = "callsMethodWithExceptionAndTryCatchFinally"; + String innerMethodName = "throwsAnException"; + // we need to create an new loader with a new classPool + Loader loader = this.createLoader(); + ClassPool classPool = new ClassPool(); + classPool.insertClassPath(new LoaderClassPath(loader)); + loader.setClassPool(classPool); + CtMethod ctMethod = classPool.getMethod(ExceptionTestClass.class.getName(), methodName); + CtMethod innerCtMethod = classPool.getMethod(ExceptionTestClass.class.getName(), innerMethodName); + CtClass exceptionClazz = classPool.get(MyTestException.class.getName()); + + long methodId = 9L; + long innerMethodId = 11L; + long sensorTypeId = 10L; + long constructorId = 5L; + + MyTestException exceptionObject = MyTestException.class.newInstance(); + + // initializing / mocking the RegisteredSensorConfig for the + // constructors and some needed variables + RegisteredSensorConfig registeredConstructorSensorConfig = mock(RegisteredSensorConfig.class); + when(registeredConstructorSensorConfig.getId()).thenReturn(sensorTypeId); + when(registeredConstructorSensorConfig.isConstructor()).thenReturn(true); + when(registeredConstructorSensorConfig.getTargetClassName()).thenReturn(exceptionClazz.getSimpleName()); + when(registeredConstructorSensorConfig.getTargetMethodName()).thenReturn(exceptionClazz.getSimpleName()); + when(registeredConstructorSensorConfig.getModifiers()).thenReturn(exceptionClazz.getModifiers()); + when(idManager.registerMethod(registeredConstructorSensorConfig)).thenReturn(constructorId); + + // stuff for the methods + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + RegisteredSensorConfig innerRegisteredSensorConfig = mock(RegisteredSensorConfig.class); + when(registeredSensorConfig.getId()).thenReturn(sensorTypeId); + when(innerRegisteredSensorConfig.getId()).thenReturn(sensorTypeId); + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + when(idManager.registerMethod(innerRegisteredSensorConfig)).thenReturn(innerMethodId); + when(configurationStorage.isExceptionSensorActivated()).thenReturn(true); + when(configurationStorage.isEnhancedExceptionSensorActivated()).thenReturn(false); + + MethodSensorTypeConfig sensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(sensorTypeConfig.getName()).thenReturn("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"); + when(sensorTypeConfig.getId()).thenReturn(sensorTypeId); + List sensorTypeConfigs = new ArrayList(); + sensorTypeConfigs.add(sensorTypeConfig); + + when(registeredSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + when(innerRegisteredSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + when(registeredConstructorSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + + // instrumenting the first method + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + verify(idManager, times(1)).addSensorTypeToMethod(sensorTypeId, methodId); + verify(hookDispatcher, times(1)).addMethodMapping(methodId, registeredSensorConfig); + + // instrumenting the called inner method + hookInstrumenter.addMethodHook(innerCtMethod, innerRegisteredSensorConfig); + verify(idManager, times(1)).addSensorTypeToMethod(sensorTypeId, innerMethodId); + verify(hookDispatcher, times(1)).addMethodMapping(innerMethodId, innerRegisteredSensorConfig); + + // instrumenting the constructors + CtConstructor[] constructors = exceptionClazz.getConstructors(); + for (int i = 0; i < constructors.length; i++) { + hookInstrumenter.addConstructorHook(constructors[i], registeredConstructorSensorConfig); + } + + verify(idManager, times(3)).addSensorTypeToMethod(sensorTypeId, constructorId); + verify(hookDispatcher, times(3)).addConstructorMapping(constructorId, registeredConstructorSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + // first method + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, testClass, new Object[0], null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, testClass, new Object[0], null); + + // inner method + verify(hookDispatcher).dispatchMethodBeforeBody(innerMethodId, testClass, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(innerMethodId, testClass, new Object[0], null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(innerMethodId, testClass, new Object[0], null); + + verify(hookDispatcher).dispatchConstructorBeforeBody(eq(constructorId), (Object[]) anyObject()); + verify(hookDispatcher).dispatchConstructorAfterBody(eq(constructorId), argThat(new MyTestExceptionVerifier(exceptionObject)), (Object[]) anyObject()); + // verify that no exception event is dispatched + verify(hookDispatcher, times(0)).dispatchOnThrowInBody(eq(innerMethodId), eq(testClass), (Object[]) anyObject(), argThat(new MyTestExceptionVerifier(exceptionObject))); + verify(hookDispatcher, times(0)).dispatchBeforeCatch(eq(methodId), argThat(new MyTestExceptionVerifier(exceptionObject))); + verifyNoMoreInteractions(hookDispatcher); + } + + @Test + public void everythingInstrumentedByExceptionSensorWithStaticMethods() throws Exception { + String methodName = "callsStaticMethodWithException"; + String innerMethodName = "staticThrowsAnException"; + // we need to create an new loader with a new classPool + Loader loader = this.createLoader(); + ClassPool classPool = new ClassPool(); + classPool.insertClassPath(new LoaderClassPath(loader)); + loader.setClassPool(classPool); + CtMethod ctMethod = classPool.getMethod(ExceptionTestClass.class.getName(), methodName); + CtMethod innerCtMethod = classPool.getMethod(ExceptionTestClass.class.getName(), innerMethodName); + CtClass exceptionClazz = classPool.get(MyTestException.class.getName()); + + long methodId = 9L; + long innerMethodId = 11L; + long sensorTypeId = 10L; + long constructorId = 5L; + + MyTestException exceptionObject = MyTestException.class.newInstance(); + + // initializing / mocking the RegisteredSensorConfig for the + // constructors and some needed variables + RegisteredSensorConfig registeredConstructorSensorConfig = mock(RegisteredSensorConfig.class); + when(registeredConstructorSensorConfig.getId()).thenReturn(sensorTypeId); + when(registeredConstructorSensorConfig.isConstructor()).thenReturn(true); + when(registeredConstructorSensorConfig.getTargetClassName()).thenReturn(exceptionClazz.getSimpleName()); + when(registeredConstructorSensorConfig.getTargetMethodName()).thenReturn(exceptionClazz.getSimpleName()); + when(registeredConstructorSensorConfig.getModifiers()).thenReturn(exceptionClazz.getModifiers()); + when(idManager.registerMethod(registeredConstructorSensorConfig)).thenReturn(constructorId); + + // stuff for the methods + RegisteredSensorConfig registeredSensorConfig = mock(RegisteredSensorConfig.class); + RegisteredSensorConfig innerRegisteredSensorConfig = mock(RegisteredSensorConfig.class); + when(registeredSensorConfig.getId()).thenReturn(sensorTypeId); + when(innerRegisteredSensorConfig.getId()).thenReturn(sensorTypeId); + when(idManager.registerMethod(registeredSensorConfig)).thenReturn(methodId); + when(idManager.registerMethod(innerRegisteredSensorConfig)).thenReturn(innerMethodId); + when(configurationStorage.isExceptionSensorActivated()).thenReturn(true); + when(configurationStorage.isEnhancedExceptionSensorActivated()).thenReturn(true); + + MethodSensorTypeConfig sensorTypeConfig = mock(MethodSensorTypeConfig.class); + when(sensorTypeConfig.getName()).thenReturn("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor"); + when(sensorTypeConfig.getId()).thenReturn(sensorTypeId); + List sensorTypeConfigs = new ArrayList(); + sensorTypeConfigs.add(sensorTypeConfig); + + when(registeredSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + when(innerRegisteredSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + when(registeredConstructorSensorConfig.getSensorTypeConfigs()).thenReturn(sensorTypeConfigs); + + // instrumenting the first method + hookInstrumenter.addMethodHook(ctMethod, registeredSensorConfig); + verify(idManager, times(1)).addSensorTypeToMethod(sensorTypeId, methodId); + verify(hookDispatcher, times(1)).addMethodMapping(methodId, registeredSensorConfig); + + // instrumenting the called inner method + hookInstrumenter.addMethodHook(innerCtMethod, innerRegisteredSensorConfig); + verify(idManager, times(1)).addSensorTypeToMethod(sensorTypeId, innerMethodId); + verify(hookDispatcher, times(1)).addMethodMapping(innerMethodId, innerRegisteredSensorConfig); + + // instrumenting the constructors + CtConstructor[] constructors = exceptionClazz.getConstructors(); + for (int i = 0; i < constructors.length; i++) { + hookInstrumenter.addConstructorHook(constructors[i], registeredConstructorSensorConfig); + } + verify(idManager, times(3)).addSensorTypeToMethod(sensorTypeId, constructorId); + verify(hookDispatcher, times(3)).addConstructorMapping(constructorId, registeredConstructorSensorConfig); + + Object testClass = this.createInstance(loader, ctMethod); + this.callMethod(testClass, methodName, null); + + // first method + verify(hookDispatcher).dispatchMethodBeforeBody(methodId, null, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(methodId, null, new Object[0], null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(methodId, null, new Object[0], null); + + // inner method + verify(hookDispatcher).dispatchMethodBeforeBody(innerMethodId, null, new Object[0]); + verify(hookDispatcher).dispatchFirstMethodAfterBody(innerMethodId, null, new Object[0], null); + verify(hookDispatcher).dispatchSecondMethodAfterBody(innerMethodId, null, new Object[0], null); + + verify(hookDispatcher).dispatchConstructorBeforeBody(eq(constructorId), (Object[]) anyObject()); + verify(hookDispatcher).dispatchConstructorAfterBody(eq(constructorId), argThat(new MyTestExceptionVerifier(exceptionObject)), (Object[]) anyObject()); + verify(hookDispatcher).dispatchOnThrowInBody(eq(innerMethodId), isNull(), (Object[]) anyObject(), argThat(new ThrowableVerifier(exceptionObject))); + verify(hookDispatcher).dispatchBeforeCatch(eq(methodId), argThat(new ThrowableVerifier(exceptionObject))); + verifyNoMoreInteractions(hookDispatcher); + } + + /** + * Inner class used to verify the contents of MyTestException objects. + */ + private static class MyTestExceptionVerifier extends ArgumentMatcher { + @SuppressWarnings("unused") + private final MyTestException myTestException; + + public MyTestExceptionVerifier(MyTestException myTestException) { + this.myTestException = myTestException; + } + + @Override + public boolean matches(Object object) { + // TODO ET: why does this test fail? is it because object has + // another class loader? + // MyTestException is loaded by sun.misc.Launcher$AppClassLoader + // object is loaded by javassist.Loader + + // if (!MyTestException.class.isInstance(object)) { + // return false; + // } + // + // MyTestException otherException = (MyTestException) object; + // + // if ((null != myTestException.getCause()) && + // !myTestException.getCause().equals(otherException.getCause())) { + // return false; + // } + // if ((null != myTestException.getMessage()) && + // !myTestException.getMessage + // ().equals(otherException.getMessage())) { + // return false; + // } + + return true; + } + } + + /** + * Inner class used to verify the contents of MyTestException objects. + */ + private static class ThrowableVerifier extends ArgumentMatcher { + @SuppressWarnings("unused") + private final Object exceptionObject; + + public ThrowableVerifier(Object exceptionObject) { + this.exceptionObject = exceptionObject; + } + + @Override + public boolean matches(Object object) { + // TODO ET: object has different class loader + + // if (!exceptionObject.getClass().isInstance(object)) { + // return false; + // } + return true; + } + } + + private static class ObjectVerifier extends ArgumentMatcher { + @SuppressWarnings("unused") + private final Object object; + + public ObjectVerifier(Object object) { + this.object = object; + } + + @Override + public boolean matches(Object obj) { + // TODO ET: object has different class loader + + // if (!obj.getClass().isInstance(object.getClass())) { + // return false; + // } + return true; + } + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/sending/impl/ListSizeStrategyTest.java b/Agent/test/info/novatec/inspectit/agent/sending/impl/ListSizeStrategyTest.java new file mode 100644 index 000000000..dfe90a8cf --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sending/impl/ListSizeStrategyTest.java @@ -0,0 +1,84 @@ +package info.novatec.inspectit.agent.sending.impl; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.MockInit; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.ListListener; +import info.novatec.inspectit.agent.sending.ISendingStrategy; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mockito.Mock; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class ListSizeStrategyTest extends MockInit { + + @Mock + private ICoreService coreService; + + private ISendingStrategy sendingStrategy; + + @BeforeMethod + public void initTestClass() { + sendingStrategy = new ListSizeStrategy(); + } + + @Test + public void startStop() { + sendingStrategy.start(coreService); + verify(coreService).addListListener((ListListener) sendingStrategy); + + sendingStrategy.stop(); + verify(coreService).removeListListener((ListListener) sendingStrategy); + + verifyNoMoreInteractions(coreService); + } + + @SuppressWarnings("unchecked") + @Test + public void contentChanged() { + sendingStrategy.start(coreService); + List list = mock(List.class); + + ((ListListener) sendingStrategy).contentChanged(list); + + verify(list).size(); + + verifyNoMoreInteractions(list); + } + + @SuppressWarnings("unchecked") + @Test + public void fireSending() { + sendingStrategy.start(coreService); + List list = mock(List.class); + when(list.size()).thenReturn(11); + + ((ListListener) sendingStrategy).contentChanged(list); + + verify(coreService).sendData(); + } + + @SuppressWarnings("unchecked") + @Test + public void fireSendingModifiedListSize() { + Map settings = new HashMap(); + settings.put("size", "3"); + sendingStrategy.init(settings); + sendingStrategy.start(coreService); + List list = mock(List.class); + when(list.size()).thenReturn(5); + + ((ListListener) sendingStrategy).contentChanged(list); + + verify(coreService).sendData(); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/sending/impl/TimeStrategyTest.java b/Agent/test/info/novatec/inspectit/agent/sending/impl/TimeStrategyTest.java new file mode 100644 index 000000000..1d972e6a3 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sending/impl/TimeStrategyTest.java @@ -0,0 +1,59 @@ +package info.novatec.inspectit.agent.sending.impl; + +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import info.novatec.inspectit.agent.MockInit; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.sending.ISendingStrategy; + +import java.util.HashMap; +import java.util.Map; + +import org.mockito.Mock; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class TimeStrategyTest extends MockInit { + + @Mock + private ICoreService coreService; + + private ISendingStrategy sendingStrategy; + + @BeforeMethod + public void initTestClass() { + sendingStrategy = new TimeStrategy(); + } + + @Test + public void startStop() { + sendingStrategy.start(coreService); + + sendingStrategy.stop(); + + verifyZeroInteractions(coreService); + } + + /** + * This test could fail, thus it invocation count is increased to 5, which means that this test + * will be executed 5 times and only 60% of the tests need to be completed successfully. + */ + @Test(invocationCount = 5, successPercentage = 60) + public void sendAfterOneSecond() throws InterruptedException { + Map settings = new HashMap(); + settings.put("time", "1000"); + sendingStrategy.init(settings); + sendingStrategy.start(coreService); + + synchronized (this) { + wait(1500L); + } + + // should be called at least once, but sometimes it could be even two + // times. + verify(coreService, atLeastOnce()).sendData(); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/exception/ExceptionSensorHookTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/exception/ExceptionSensorHookTest.java new file mode 100644 index 000000000..ac9341bd3 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/exception/ExceptionSensorHookTest.java @@ -0,0 +1,415 @@ +package info.novatec.inspectit.agent.sensor.exception; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.analyzer.classes.MyTestException; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.communication.ExceptionEvent; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.util.StringConstraint; + +import java.lang.reflect.Field; +import java.sql.Timestamp; +import java.util.HashMap; +import java.util.Map; + +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class ExceptionSensorHookTest extends AbstractLogSupport { + @Mock + private IIdManager idManager; + + @Mock + private ICoreService coreService; + + @Mock + private RegisteredSensorConfig registeredSensorConfig; + + private Map parameter; + + private int stringLength; + + private ExceptionSensorHook exceptionHook; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + stringLength = 1000; + parameter = new HashMap(); + parameter.put("stringLength", String.valueOf(stringLength)); + exceptionHook = new ExceptionSensorHook(idManager, parameter); + } + + @Test + public void throwableObjectWasCreated() throws InstantiationException, IllegalAccessException, IdNotAvailableException { + long constructorId = 5L; + long sensorTypeId = 3L; + long platformId = 1L; + long registeredConstructorId = 15L; + long registeredSensorTypeId = 13L; + + Object[] parameters = new Object[0]; + MyTestException exceptionObject = MyTestException.class.newInstance(); + when(idManager.getRegisteredMethodId(constructorId)).thenReturn(registeredConstructorId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + when(idManager.getPlatformId()).thenReturn(platformId); + + when(registeredSensorConfig.getQualifiedTargetClassName()).thenReturn(MyTestException.class.getName()); + + exceptionHook.afterConstructor(coreService, constructorId, sensorTypeId, exceptionObject, parameters, registeredSensorConfig); + verify(idManager, times(1)).getRegisteredMethodId(constructorId); + verify(idManager, times(1)).getRegisteredSensorTypeId(sensorTypeId); + verify(idManager, times(1)).getPlatformId(); + + verifyNoMoreInteractions(idManager); + // verify(coreService, never()); + } + + @Test + public void throwableObjectDifferentThenRSCTargetClass() throws InstantiationException, IllegalAccessException, IdNotAvailableException { + long constructorId = 5L; + long sensorTypeId = 3L; + long platformId = 1L; + long registeredConstructorId = 15L; + long registeredSensorTypeId = 13L; + + Object[] parameters = new Object[0]; + Exception exceptionObject = Exception.class.newInstance(); + when(idManager.getRegisteredMethodId(constructorId)).thenReturn(registeredConstructorId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + when(idManager.getPlatformId()).thenReturn(platformId); + + when(registeredSensorConfig.getQualifiedTargetClassName()).thenReturn(MyTestException.class.getName()); + + exceptionHook.afterConstructor(coreService, constructorId, sensorTypeId, exceptionObject, parameters, registeredSensorConfig); + + verifyNoMoreInteractions(idManager); + } + + @Test + public void throwableObjectCreatedThrownAndHandled() throws InstantiationException, IllegalAccessException, IdNotAvailableException { + long methodId = 5L; + long constructorId = 4L; + long sensorTypeId = 3L; + long platformId = 1L; + long registeredMethodId = 15L; + long registeredSensorTypeId = 13L; + long registeredConstructorId = 14L; + long methodIdTwo = 20L; + long registeredMethodIdTwo = 22L; + + Object[] parameters = new Object[0]; + Object object = mock(Object.class); + MyTestException exceptionObject = MyTestException.class.newInstance(); + + ExceptionSensorData exceptionSensorData = new ExceptionSensorData(new Timestamp(System.currentTimeMillis()), platformId, registeredSensorTypeId, registeredMethodIdTwo); + exceptionSensorData.setErrorMessage(exceptionObject.getMessage()); + exceptionSensorData.setThrowableIdentityHashCode(System.identityHashCode(exceptionObject)); + + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + when(idManager.getRegisteredMethodId(methodIdTwo)).thenReturn(registeredMethodIdTwo); + when(idManager.getRegisteredMethodId(constructorId)).thenReturn(registeredConstructorId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + when(idManager.getPlatformId()).thenReturn(platformId); + + when(registeredSensorConfig.getQualifiedTargetClassName()).thenReturn(MyTestException.class.getName()); + + exceptionSensorData.setExceptionEvent(ExceptionEvent.CREATED); + exceptionHook.afterConstructor(coreService, constructorId, sensorTypeId, exceptionObject, parameters, registeredSensorConfig); + verify(idManager, times(1)).getRegisteredMethodId(constructorId); + verify(idManager, times(1)).getRegisteredSensorTypeId(sensorTypeId); + verify(idManager, times(1)).getPlatformId(); + verify(coreService, times(1)).addExceptionSensorData(eq(registeredSensorTypeId), eq(exceptionSensorData.getThrowableIdentityHashCode()), + argThat(new ExceptionSensorDataVerifier(exceptionSensorData))); + + exceptionSensorData.setExceptionEvent(ExceptionEvent.PASSED); + exceptionHook.dispatchOnThrowInBody(coreService, methodId, sensorTypeId, object, exceptionObject, parameters, registeredSensorConfig); + verify(idManager, times(1)).getRegisteredMethodId(methodId); + verify(idManager, times(2)).getRegisteredSensorTypeId(sensorTypeId); + verify(idManager, times(2)).getPlatformId(); + verify(coreService, times(1)).addExceptionSensorData(eq(registeredSensorTypeId), eq(exceptionSensorData.getThrowableIdentityHashCode()), + argThat(new ExceptionSensorDataVerifier(exceptionSensorData))); + + exceptionSensorData.setExceptionEvent(ExceptionEvent.HANDLED); + exceptionHook.dispatchBeforeCatchBody(coreService, methodIdTwo, sensorTypeId, exceptionObject, registeredSensorConfig); + verify(idManager, times(1)).getRegisteredMethodId(methodIdTwo); + verify(idManager, times(3)).getRegisteredSensorTypeId(sensorTypeId); + verify(idManager, times(3)).getPlatformId(); + verify(coreService, times(1)).addExceptionSensorData(eq(registeredSensorTypeId), eq(exceptionSensorData.getThrowableIdentityHashCode()), + argThat(new ExceptionSensorDataVerifier(exceptionSensorData))); + + verifyNoMoreInteractions(idManager, coreService); + } + + @Test + public void differentThrowableObjectsCreatedAndThrown() throws InstantiationException, IllegalAccessException, IdNotAvailableException { + long methodId = 5L; + long constructorId = 4L; + long sensorTypeId = 3L; + long platformId = 1L; + long registeredMethodId = 15L; + long registeredSensorTypeId = 13L; + long registeredConstructorId = 14L; + + Object[] parameters = new Object[0]; + Object object = mock(Object.class); + MyTestException firstExceptionObject = MyTestException.class.newInstance(); + MyTestException secondExceptionObject = MyTestException.class.newInstance(); + + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + when(idManager.getRegisteredMethodId(constructorId)).thenReturn(registeredConstructorId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + when(idManager.getPlatformId()).thenReturn(platformId); + + when(registeredSensorConfig.getQualifiedTargetClassName()).thenReturn(MyTestException.class.getName()); + + exceptionHook.afterConstructor(coreService, constructorId, sensorTypeId, firstExceptionObject, parameters, registeredSensorConfig); + verify(idManager, times(1)).getRegisteredMethodId(constructorId); + verify(idManager, times(1)).getRegisteredSensorTypeId(sensorTypeId); + verify(idManager, times(1)).getPlatformId(); + // verify(coreService, + // times(1)).addExceptionSensorData(eq(registeredSensorTypeId), + // argThat(new ExceptionSensorDataVerifier(exceptionSensorData))); + + exceptionHook.dispatchOnThrowInBody(coreService, methodId, sensorTypeId, object, secondExceptionObject, parameters, registeredSensorConfig); + verify(idManager, times(1)).getRegisteredMethodId(methodId); + verify(idManager, times(2)).getRegisteredSensorTypeId(sensorTypeId); + verify(idManager, times(2)).getPlatformId(); + + verifyNoMoreInteractions(idManager); + } + + @Test + public void throwableHasCause() throws InstantiationException, IllegalAccessException, IdNotAvailableException, SecurityException, NoSuchFieldException { + long methodId = 5L; + long constructorId = 4L; + long sensorTypeId = 3L; + long platformId = 1L; + long registeredMethodId = 15L; + long registeredSensorTypeId = 13L; + long registeredConstructorId = 14L; + long methodIdTwo = 20L; + long registeredMethodIdTwo = 22L; + + Object[] parameters = new Object[0]; + Object object = mock(Object.class); + MyTestException exceptionObject = MyTestException.class.newInstance(); + Throwable cause = Throwable.class.newInstance(); + + // setting the cause at the exceptionObject + // we can only access the cause field from the overall superclass + // Throwable + Field causeField = exceptionObject.getClass().getSuperclass().getSuperclass().getDeclaredField("cause"); + causeField.setAccessible(true); + causeField.set(exceptionObject, cause); + + ExceptionSensorData exceptionSensorData = new ExceptionSensorData(new Timestamp(System.currentTimeMillis()), platformId, registeredSensorTypeId, registeredMethodIdTwo); + exceptionSensorData.setErrorMessage(exceptionObject.getMessage()); + exceptionSensorData.setCause(exceptionObject.getCause().getClass().getName()); + exceptionSensorData.setThrowableIdentityHashCode(System.identityHashCode(exceptionObject)); + + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + when(idManager.getRegisteredMethodId(methodIdTwo)).thenReturn(registeredMethodIdTwo); + when(idManager.getRegisteredMethodId(constructorId)).thenReturn(registeredConstructorId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + when(idManager.getPlatformId()).thenReturn(platformId); + + when(registeredSensorConfig.getQualifiedTargetClassName()).thenReturn(MyTestException.class.getName()); + + exceptionSensorData.setExceptionEvent(ExceptionEvent.CREATED); + exceptionHook.afterConstructor(coreService, constructorId, sensorTypeId, exceptionObject, parameters, registeredSensorConfig); + verify(idManager, times(1)).getRegisteredMethodId(constructorId); + verify(idManager, times(1)).getRegisteredSensorTypeId(sensorTypeId); + verify(idManager, times(1)).getPlatformId(); + verify(coreService, times(1)).addExceptionSensorData(eq(registeredSensorTypeId), eq(exceptionSensorData.getThrowableIdentityHashCode()), + argThat(new ExceptionSensorDataVerifier(exceptionSensorData))); + assertThat(exceptionSensorData.getCause(), is(equalTo(cause.getClass().getName()))); + + // resetting the cause to null as we need the cause only in the first + // data object + exceptionSensorData.setCause(null); + + exceptionSensorData.setExceptionEvent(ExceptionEvent.PASSED); + exceptionHook.dispatchOnThrowInBody(coreService, methodId, sensorTypeId, object, exceptionObject, parameters, registeredSensorConfig); + verify(idManager, times(1)).getRegisteredMethodId(methodId); + verify(idManager, times(2)).getRegisteredSensorTypeId(sensorTypeId); + verify(idManager, times(2)).getPlatformId(); + verify(coreService, times(1)).addExceptionSensorData(eq(registeredSensorTypeId), eq(exceptionSensorData.getThrowableIdentityHashCode()), + argThat(new ExceptionSensorDataVerifier(exceptionSensorData))); + + exceptionSensorData.setExceptionEvent(ExceptionEvent.HANDLED); + exceptionHook.dispatchBeforeCatchBody(coreService, methodIdTwo, sensorTypeId, exceptionObject, registeredSensorConfig); + verify(idManager, times(1)).getRegisteredMethodId(methodIdTwo); + verify(idManager, times(3)).getRegisteredSensorTypeId(sensorTypeId); + verify(idManager, times(3)).getPlatformId(); + verify(coreService, times(1)).addExceptionSensorData(eq(registeredSensorTypeId), eq(exceptionSensorData.getThrowableIdentityHashCode()), + argThat(new ExceptionSensorDataVerifier(exceptionSensorData))); + + verifyNoMoreInteractions(idManager, coreService); + } + + @Test + public void valueTooLong() throws InstantiationException, IllegalAccessException, IdNotAvailableException { + long constructorId = 5L; + long sensorTypeId = 3L; + long platformId = 1L; + long registeredConstructorId = 15L; + long registeredSensorTypeId = 13L; + + Object[] parameters = new Object[0]; + StringConstraint strConstraint = new StringConstraint(parameter); + + // the actual error message is too long, so it should be cropped later on + String exceptionMessage = fillString('x', stringLength + 1); + MyTestException exceptionObject = new MyTestException(exceptionMessage); + + ExceptionSensorData exceptionSensorData = new ExceptionSensorData(new Timestamp(System.currentTimeMillis()), platformId, registeredSensorTypeId, registeredConstructorId); + exceptionSensorData.setExceptionEvent(ExceptionEvent.CREATED); + exceptionSensorData.setThrowableIdentityHashCode(System.identityHashCode(exceptionObject)); + + // the actual error message to be verified against + exceptionSensorData.setErrorMessage(strConstraint.crop(exceptionMessage)); + + when(idManager.getRegisteredMethodId(constructorId)).thenReturn(registeredConstructorId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + when(idManager.getPlatformId()).thenReturn(platformId); + + when(registeredSensorConfig.getQualifiedTargetClassName()).thenReturn(MyTestException.class.getName()); + + exceptionHook.afterConstructor(coreService, constructorId, sensorTypeId, exceptionObject, parameters, registeredSensorConfig); + verify(idManager, times(1)).getRegisteredMethodId(constructorId); + verify(idManager, times(1)).getRegisteredSensorTypeId(sensorTypeId); + verify(idManager, times(1)).getPlatformId(); + verify(coreService, times(1)).addExceptionSensorData(eq(registeredSensorTypeId), eq(exceptionSensorData.getThrowableIdentityHashCode()), + argThat(new ExceptionSensorDataVerifier(exceptionSensorData))); + + verifyNoMoreInteractions(idManager); + } + + @Test + public void platformIdNotAvailable() throws IdNotAvailableException { + // set up data + long methodId = 3L; + long constructorId = 7L; + long exceptionSensorTypeId = 11L; + Object object = mock(Object.class); + MyTestException exceptionObject = mock(MyTestException.class); + Object[] parameters = new Object[0]; + + doThrow(new IdNotAvailableException("")).when(idManager).getPlatformId(); + + exceptionHook.afterConstructor(coreService, constructorId, exceptionSensorTypeId, exceptionObject, parameters, registeredSensorConfig); + exceptionHook.dispatchOnThrowInBody(coreService, methodId, exceptionSensorTypeId, object, exceptionObject, parameters, registeredSensorConfig); + exceptionHook.dispatchBeforeCatchBody(coreService, methodId, exceptionSensorTypeId, exceptionObject, registeredSensorConfig); + + verify(coreService, never()).addExceptionSensorData(anyLong(), anyInt(), (ExceptionSensorData) isNull()); + } + + @Test + public void methodIdNotAvailable() throws IdNotAvailableException { + // set up data + long platformId = 1L; + long methodId = 3L; + long exceptionSensorTypeId = 11L; + long constructorId = 7L; + Object object = mock(Object.class); + MyTestException exceptionObject = mock(MyTestException.class); + Object[] parameters = new Object[0]; + + when(idManager.getPlatformId()).thenReturn(platformId); + doThrow(new IdNotAvailableException("")).when(idManager).getRegisteredMethodId(methodId); + + exceptionHook.afterConstructor(coreService, constructorId, exceptionSensorTypeId, exceptionObject, parameters, registeredSensorConfig); + exceptionHook.dispatchOnThrowInBody(coreService, methodId, exceptionSensorTypeId, object, exceptionObject, parameters, registeredSensorConfig); + exceptionHook.dispatchBeforeCatchBody(coreService, methodId, exceptionSensorTypeId, exceptionObject, registeredSensorConfig); + + verify(coreService, never()).addExceptionSensorData(anyLong(), anyInt(), (ExceptionSensorData) isNull()); + } + + @Test + public void sensorTypeIdNotAvailable() throws IdNotAvailableException { + // set up data + long platformId = 1L; + long methodId = 3L; + long registeredMethodId = 13L; + long exceptionSensorTypeId = 11L; + long constructorId = 7L; + Object object = mock(Object.class); + MyTestException exceptionObject = mock(MyTestException.class); + Object[] parameters = new Object[0]; + + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + doThrow(new IdNotAvailableException("")).when(idManager).getRegisteredSensorTypeId(exceptionSensorTypeId); + + exceptionHook.afterConstructor(coreService, constructorId, exceptionSensorTypeId, exceptionObject, parameters, registeredSensorConfig); + exceptionHook.dispatchOnThrowInBody(coreService, methodId, exceptionSensorTypeId, object, exceptionObject, parameters, registeredSensorConfig); + exceptionHook.dispatchBeforeCatchBody(coreService, methodId, exceptionSensorTypeId, exceptionObject, registeredSensorConfig); + + verify(coreService, never()).addExceptionSensorData(anyLong(), anyInt(), (ExceptionSensorData) isNull()); + } + + private static class ExceptionSensorDataVerifier extends ArgumentMatcher { + private final ExceptionSensorData exceptionSensorData; + + public ExceptionSensorDataVerifier(ExceptionSensorData exceptionSensorData) { + this.exceptionSensorData = exceptionSensorData; + } + + @Override + public boolean matches(Object object) { + if (!ExceptionSensorData.class.isInstance(object)) { + return false; + } + + ExceptionSensorData otherExceptionSensorData = (ExceptionSensorData) object; + if ((null != exceptionSensorData.getCause()) && !exceptionSensorData.getCause().equals(otherExceptionSensorData.getCause())) { + return false; + } + if ((null != exceptionSensorData.getErrorMessage()) && !exceptionSensorData.getErrorMessage().equals(otherExceptionSensorData.getErrorMessage())) { + return false; + } + if (!exceptionSensorData.getExceptionEvent().equals(otherExceptionSensorData.getExceptionEvent())) { + return false; + } + if ((null != exceptionSensorData.getThrowableType()) && !exceptionSensorData.getThrowableType().equals(otherExceptionSensorData.getThrowableType())) { + return false; + } + if (exceptionSensorData.getThrowableIdentityHashCode() != otherExceptionSensorData.getThrowableIdentityHashCode()) { + return false; + } + + return true; + } + + } + + public String fillString(char character, int count) { + // creates a string of 'x' repeating characters + char[] chars = new char[count]; + while (count > 0) { + chars[--count] = character; + } + return new String(chars); + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/method/averagetimer/AverageTimerHookTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/method/averagetimer/AverageTimerHookTest.java new file mode 100644 index 000000000..0e1e8197b --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/method/averagetimer/AverageTimerHookTest.java @@ -0,0 +1,445 @@ +package info.novatec.inspectit.agent.sensor.method.averagetimer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.config.IPropertyAccessor; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.util.ObjectUtils; +import info.novatec.inspectit.util.Timer; + +import java.util.Map; + +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class AverageTimerHookTest extends AbstractLogSupport { + + @Mock + private Timer timer; + + @Mock + private IIdManager idManager; + + @Mock + private IPropertyAccessor propertyAccessor; + + @Mock + private ICoreService coreService; + + @Mock + private RegisteredSensorConfig registeredSensorConfig; + + @Mock + private Map parameter; + + private AverageTimerHook averageTimerHook; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + averageTimerHook = new AverageTimerHook(timer, idManager, propertyAccessor, parameter); + } + + @Test + public void oneRecord() throws IdNotAvailableException { + // set up data + long platformId = 1L; + long methodId = 3L; + long registeredMethodId = 13L; + long sensorTypeId = 11L; + long registeredSensorTypeId = 7L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + + averageTimerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + verify(timer, times(1)).getCurrentTime(); + + averageTimerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(timer, times(2)).getCurrentTime(); + + averageTimerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(idManager).getPlatformId(); + verify(idManager).getRegisteredMethodId(methodId); + verify(idManager).getRegisteredSensorTypeId(sensorTypeId); + verify(coreService).getMethodSensorData(sensorTypeId, methodId, null); + verify(registeredSensorConfig).isPropertyAccess(); + + TimerData timerData = new TimerData(); + timerData.setPlatformIdent(platformId); + timerData.setMethodIdent(registeredMethodId); + timerData.setSensorTypeIdent(registeredSensorTypeId); + timerData.setCount(1L); + timerData.setDuration(secondTimerValue - firstTimerValue); + timerData.calculateMax(secondTimerValue - firstTimerValue); + timerData.calculateMin(secondTimerValue - firstTimerValue); + verify(coreService).addMethodSensorData(eq(sensorTypeId), eq(methodId), (String) eq(null), argThat(new TimerDataVerifier(timerData))); + + verifyNoMoreInteractions(timer, idManager, coreService, registeredSensorConfig); + verifyZeroInteractions(propertyAccessor, object, result); + } + + @Test + public void twoRecords() throws IdNotAvailableException { + long platformId = 1L; + long methodIdOne = 3L; + long registeredMethodIdOne = 13L; + long methodIdTwo = 9L; + long registeredMethodIdTwo = 15L; + long sensorTypeId = 11L; + long registeredSensorTypeId = 7L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + Double thirdTimerValue = 1578.92d; + Double fourthTimerValue = 2319.712d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue).thenReturn(thirdTimerValue).thenReturn(fourthTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodIdOne)).thenReturn(registeredMethodIdOne); + when(idManager.getRegisteredMethodId(methodIdTwo)).thenReturn(registeredMethodIdTwo); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + + averageTimerHook.beforeBody(methodIdOne, sensorTypeId, object, parameters, registeredSensorConfig); + averageTimerHook.beforeBody(methodIdTwo, sensorTypeId, object, parameters, registeredSensorConfig); + + averageTimerHook.firstAfterBody(methodIdTwo, sensorTypeId, object, parameters, result, registeredSensorConfig); + averageTimerHook.secondAfterBody(coreService, methodIdTwo, sensorTypeId, object, parameters, result, registeredSensorConfig); + TimerData timerDataTwo = new TimerData(); + timerDataTwo.setPlatformIdent(platformId); + timerDataTwo.setMethodIdent(registeredMethodIdTwo); + timerDataTwo.setSensorTypeIdent(registeredSensorTypeId); + timerDataTwo.setCount(1L); + timerDataTwo.setDuration(thirdTimerValue - secondTimerValue); + timerDataTwo.calculateMax(thirdTimerValue - secondTimerValue); + timerDataTwo.calculateMin(thirdTimerValue - secondTimerValue); + verify(coreService).addMethodSensorData(eq(sensorTypeId), eq(methodIdTwo), (String) eq(null), argThat(new TimerDataVerifier(timerDataTwo))); + + averageTimerHook.firstAfterBody(methodIdOne, sensorTypeId, object, parameters, result, registeredSensorConfig); + averageTimerHook.secondAfterBody(coreService, methodIdOne, sensorTypeId, object, parameters, result, registeredSensorConfig); + TimerData timerDataOne = new TimerData(); + timerDataOne.setPlatformIdent(platformId); + timerDataOne.setMethodIdent(registeredMethodIdOne); + timerDataOne.setSensorTypeIdent(registeredSensorTypeId); + timerDataOne.setCount(1L); + timerDataOne.setDuration(fourthTimerValue - firstTimerValue); + timerDataOne.calculateMax(fourthTimerValue - firstTimerValue); + timerDataOne.calculateMin(fourthTimerValue - firstTimerValue); + verify(coreService).addMethodSensorData(eq(sensorTypeId), eq(methodIdOne), (String) eq(null), argThat(new TimerDataVerifier(timerDataOne))); + } + + @Test + public void sameMethodTwice() throws IdNotAvailableException { + // set up data + long platformId = 1L; + long methodId = 3L; + long registeredMethodId = 13L; + long sensorTypeId = 11L; + long registeredSensorTypeId = 7L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.0d; + Double secondTimerValue = 1323.0d; + Double thirdTimerValue = 1894.0d; + Double fourthTimerValue = 2812.0d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue).thenReturn(thirdTimerValue).thenReturn(fourthTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + + // First call + averageTimerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + verify(timer, times(1)).getCurrentTime(); + + averageTimerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(timer, times(2)).getCurrentTime(); + + averageTimerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(idManager).getPlatformId(); + verify(idManager).getRegisteredMethodId(methodId); + verify(idManager).getRegisteredSensorTypeId(sensorTypeId); + verify(coreService).getMethodSensorData(sensorTypeId, methodId, null); + verify(registeredSensorConfig).isPropertyAccess(); + + TimerData timerData = new TimerData(); + timerData.setPlatformIdent(platformId); + timerData.setMethodIdent(registeredMethodId); + timerData.setSensorTypeIdent(registeredSensorTypeId); + timerData.setCount(1L); + timerData.setDuration(secondTimerValue - firstTimerValue); + timerData.calculateMax(secondTimerValue - firstTimerValue); + timerData.calculateMin(secondTimerValue - firstTimerValue); + verify(coreService).addMethodSensorData(eq(sensorTypeId), eq(methodId), (String) eq(null), argThat(new TimerDataVerifier(timerData))); + + // second one + averageTimerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + verify(timer, times(3)).getCurrentTime(); + + averageTimerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(timer, times(4)).getCurrentTime(); + + when(coreService.getMethodSensorData(sensorTypeId, methodId, null)).thenReturn(timerData); + averageTimerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(coreService, times(2)).getMethodSensorData(sensorTypeId, methodId, null); + verify(registeredSensorConfig, times(2)).isPropertyAccess(); + + assertThat(timerData.getPlatformIdent(), is(equalTo(platformId))); + assertThat(timerData.getMethodIdent(), is(equalTo(registeredMethodId))); + assertThat(timerData.getSensorTypeIdent(), is(equalTo(registeredSensorTypeId))); + assertThat(timerData.getCount(), is(equalTo(2L))); + assertThat(timerData.getDuration(), is(equalTo(fourthTimerValue - thirdTimerValue + secondTimerValue - firstTimerValue))); + assertThat(timerData.getMax(), is(equalTo(fourthTimerValue - thirdTimerValue))); + assertThat(timerData.getMin(), is(equalTo(secondTimerValue - firstTimerValue))); + + verifyNoMoreInteractions(timer, idManager, coreService, registeredSensorConfig); + verifyZeroInteractions(propertyAccessor, object, result); + } + + @Test + public void newMinValue() throws IdNotAvailableException { + // set up data + long platformId = 1L; + long methodId = 3L; + long registeredMethodId = 13L; + long sensorTypeId = 11L; + long registeredSensorTypeId = 7L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.0d; + Double secondTimerValue = 1323.0d; + Double thirdTimerValue = 1894.0d; + Double fourthTimerValue = 1934.0d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue).thenReturn(thirdTimerValue).thenReturn(fourthTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + + // First call + averageTimerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + verify(timer, times(1)).getCurrentTime(); + + averageTimerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(timer, times(2)).getCurrentTime(); + + averageTimerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(idManager).getPlatformId(); + verify(idManager).getRegisteredMethodId(methodId); + verify(idManager).getRegisteredSensorTypeId(sensorTypeId); + verify(coreService).getMethodSensorData(sensorTypeId, methodId, null); + verify(registeredSensorConfig).isPropertyAccess(); + + TimerData timerData = new TimerData(); + timerData.setPlatformIdent(platformId); + timerData.setMethodIdent(registeredMethodId); + timerData.setSensorTypeIdent(registeredSensorTypeId); + timerData.setCount(1L); + timerData.setDuration(secondTimerValue - firstTimerValue); + timerData.calculateMax(secondTimerValue - firstTimerValue); + timerData.calculateMin(secondTimerValue - firstTimerValue); + verify(coreService).addMethodSensorData(eq(sensorTypeId), eq(methodId), (String) eq(null), argThat(new TimerDataVerifier(timerData))); + + // second one + averageTimerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + verify(timer, times(3)).getCurrentTime(); + + averageTimerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(timer, times(4)).getCurrentTime(); + + when(coreService.getMethodSensorData(sensorTypeId, methodId, null)).thenReturn(timerData); + averageTimerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(coreService, times(2)).getMethodSensorData(sensorTypeId, methodId, null); + verify(registeredSensorConfig, times(2)).isPropertyAccess(); + + assertThat(timerData.getPlatformIdent(), is(equalTo(platformId))); + assertThat(timerData.getMethodIdent(), is(equalTo(registeredMethodId))); + assertThat(timerData.getSensorTypeIdent(), is(equalTo(registeredSensorTypeId))); + assertThat(timerData.getCount(), is(equalTo(2L))); + assertThat(timerData.getDuration(), is(equalTo(fourthTimerValue - thirdTimerValue + secondTimerValue - firstTimerValue))); + assertThat(timerData.getMax(), is(equalTo(secondTimerValue - firstTimerValue))); + assertThat(timerData.getMin(), is(equalTo(fourthTimerValue - thirdTimerValue))); + + verifyNoMoreInteractions(timer, idManager, coreService, registeredSensorConfig); + verifyZeroInteractions(propertyAccessor, object, result); + } + + /** + * Inner class used to verify the contents of TimerData objects. + */ + private static class TimerDataVerifier extends ArgumentMatcher { + private final TimerData timerData; + + public TimerDataVerifier(TimerData timerData) { + this.timerData = timerData; + } + + @Override + public boolean matches(Object object) { + if (!TimerData.class.isInstance(object)) { + return false; + } + TimerData otherTimerData = (TimerData) object; + if (timerData.getPlatformIdent() != otherTimerData.getPlatformIdent()) { + return false; + } else if (timerData.getMethodIdent() != otherTimerData.getMethodIdent()) { + return false; + } else if (timerData.getSensorTypeIdent() != otherTimerData.getSensorTypeIdent()) { + return false; + } else if (timerData.getCount() != otherTimerData.getCount()) { + return false; + } else if (timerData.getDuration() != otherTimerData.getDuration()) { + return false; + } else if (timerData.getMax() != otherTimerData.getMax()) { + return false; + } else if (timerData.getMin() != otherTimerData.getMin()) { + return false; + } else if (timerData.getAverage() != otherTimerData.getAverage()) { + return false; + } else if (!ObjectUtils.equals(timerData.getParameterContentData(), otherTimerData.getParameterContentData())) { + return false; + } else if (timerData.getVariance() != otherTimerData.getVariance()) { + return false; + } + return true; + } + } + + @Test + public void platformIdNotAvailable() throws IdNotAvailableException { + // set up data + long methodId = 3L; + long sensorTypeId = 11L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + doThrow(new IdNotAvailableException("")).when(idManager).getPlatformId(); + + averageTimerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + averageTimerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + averageTimerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + + verify(coreService, never()).addMethodSensorData(anyLong(), anyLong(), anyString(), (MethodSensorData) isNull()); + } + + @Test + public void methodIdNotAvailable() throws IdNotAvailableException { + // set up data + long platformId = 1L; + long methodId = 3L; + long sensorTypeId = 11L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + doThrow(new IdNotAvailableException("")).when(idManager).getRegisteredMethodId(methodId); + + averageTimerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + averageTimerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + averageTimerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + + verify(coreService, never()).addMethodSensorData(anyLong(), anyLong(), anyString(), (MethodSensorData) isNull()); + } + + @Test + public void sensorTypeIdNotAvailable() throws IdNotAvailableException { + // set up data + long platformId = 1L; + long methodId = 3L; + long registeredMethodId = 13L; + long sensorTypeId = 11L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + doThrow(new IdNotAvailableException("")).when(idManager).getRegisteredSensorTypeId(sensorTypeId); + + averageTimerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + averageTimerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + averageTimerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + + verify(coreService, never()).addMethodSensorData(anyLong(), anyLong(), anyString(), (MethodSensorData) isNull()); + } + + @Test + public void propertyAccess() throws IdNotAvailableException { + // set up data + long platformId = 1L; + long methodId = 3L; + long registeredMethodId = 13L; + long sensorTypeId = 11L; + Object object = mock(Object.class); + Object[] parameters = new Object[2]; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + doThrow(new IdNotAvailableException("")).when(idManager).getRegisteredSensorTypeId(sensorTypeId); + when(registeredSensorConfig.isPropertyAccess()).thenReturn(true); + + averageTimerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + averageTimerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + averageTimerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + + verify(registeredSensorConfig, times(1)).isPropertyAccess(); + verify(propertyAccessor, times(1)).getParameterContentData(registeredSensorConfig.getPropertyAccessorList(), object, parameters, result); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/method/http/HttpHookTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/method/http/HttpHookTest.java new file mode 100644 index 000000000..26eea00f2 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/method/http/HttpHookTest.java @@ -0,0 +1,455 @@ +package info.novatec.inspectit.agent.sensor.method.http; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.util.Timer; + +import java.lang.management.ThreadMXBean; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; + +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.apache.commons.collections.MapUtils; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class HttpHookTest extends AbstractLogSupport { + + @Mock + private Timer timer; + + @Mock + private IIdManager idManager; + + @Mock + private ICoreService coreService; + + @Mock + private RegisteredSensorConfig registeredSensorConfig; + + @Mock + private ThreadMXBean threadMXBean; + + @Mock + private Object result; + + @Mock + private HttpServlet servlet; + + @Mock + private HttpServletRequest httpServletRequest; + + @Mock + private ServletRequest servletRequest; + + @Mock + private HttpSession session; + + private HttpHook httpHook; + + private long platformId = 1L; + private long methodId = 1L; + private long sensorTypeId = 3L; + private long registeredMethodId = 13L; + private long registeredSensorTypeId = 7L; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + Map settings = new HashMap(); + settings.put("sessioncapture", "false"); + when(threadMXBean.isThreadCpuTimeEnabled()).thenReturn(true); + when(threadMXBean.isThreadCpuTimeSupported()).thenReturn(true); + + Map map = new HashMap(); + MapUtils.putAll(map, new String[][] { { "sessioncapture", "true" } }); + httpHook = new HttpHook(timer, idManager, map, threadMXBean); + } + + @Test + public void oneRecordThatIsHttpWithoutReadingData() throws IdNotAvailableException { + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + Long firstCpuTimerValue = 5000L; + Long secondCpuTimerValue = 6872L; + + MethodSensorData data = new HttpTimerData(null, platformId, registeredSensorTypeId, registeredMethodId); + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + when(threadMXBean.getCurrentThreadCpuTime()).thenReturn(firstCpuTimerValue).thenReturn(secondCpuTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + + Object[] parameters = new Object[] { httpServletRequest }; + + httpHook.beforeBody(methodId, sensorTypeId, servlet, parameters, registeredSensorConfig); + + httpHook.firstAfterBody(methodId, sensorTypeId, servlet, parameters, result, registeredSensorConfig); + + httpHook.secondAfterBody(coreService, methodId, sensorTypeId, servlet, parameters, result, registeredSensorConfig); + + Mockito.verify(coreService).addMethodSensorData(Mockito.eq(registeredSensorTypeId), Mockito.eq(registeredMethodId), (String) Mockito.eq(null), + Mockito.argThat(new HttpTimerDataVerifier((HttpTimerData) data))); + + Mockito.verifyZeroInteractions(result); + } + + @Test + public void oneRecordThatIsHttpReadingDataNoCropping() throws IdNotAvailableException { + final String uri = "URI"; + final String method = "GET"; + + final String param1 = "p1"; + final String param2 = "p2"; + final String param1VReal = "value1"; + final String param2VReal1 = "value5"; + final String param2VReal2 = "value6"; + final String[] param1V = new String[] { param1VReal }; + final String[] param2V = new String[] { param2VReal1, param2VReal2 }; + final Map parameterMap = new HashMap(); + MapUtils.putAll(parameterMap, new Object[][] { { param1, param1V }, { param2, param2V } }); + + final String att1 = "a1"; + final String att2 = "a2"; + final String att1Value = "aValue1"; + final String att2Value = "aValue2"; + final Vector attributesList = new Vector(); + Collections.addAll(attributesList, att1, att2); + final Enumeration attributes = attributesList.elements(); + + final String h1 = "h1"; + final String h2 = "h2"; + final String h1Value = "hValue1"; + final String h2Value = "hValue2"; + final Vector headersList = new Vector(); + Collections.addAll(headersList, h1, h2); + final Enumeration headers = headersList.elements(); + + final String sa1 = "sa1"; + final String sa2 = "sa2"; + final String sa1Value = "saValue1"; + final String sa2Value = "saValue2"; + final Vector sessionAttributesList = new Vector(); + Collections.addAll(sessionAttributesList, sa1, sa2); + final Enumeration sessionAttributes = sessionAttributesList.elements(); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + Long firstCpuTimerValue = 5000L; + Long secondCpuTimerValue = 6872L; + + HttpTimerData tmp = new HttpTimerData(null, platformId, registeredSensorTypeId, registeredMethodId); + tmp.setRequestMethod(method); + tmp.setUri(uri); + Map attributeMap = new HashMap(); + MapUtils.putAll(attributeMap, new Object[][] { { att1, att1Value }, { att2, att2Value } }); + tmp.setAttributes(attributeMap); + + tmp.setParameters(parameterMap); + + Map headerMap = new HashMap(); + MapUtils.putAll(headerMap, new Object[][] { { h1, h1Value }, { h2, h2Value } }); + tmp.setHeaders(headerMap); + + Map sessionAtrMap = new HashMap(); + MapUtils.putAll(sessionAtrMap, new Object[][] { { sa1, sa1Value }, { sa2, sa2Value } }); + tmp.setSessionAttributes(sessionAtrMap); + MethodSensorData data = tmp; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + when(threadMXBean.getCurrentThreadCpuTime()).thenReturn(firstCpuTimerValue).thenReturn(secondCpuTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + + when(httpServletRequest.getMethod()).thenReturn(method); + when(httpServletRequest.getRequestURI()).thenReturn(uri); + when(httpServletRequest.getParameterMap()).thenReturn(parameterMap); + when(httpServletRequest.getAttributeNames()).thenReturn(attributes); + Mockito.when(httpServletRequest.getAttribute(att1)).thenReturn(att1Value); + Mockito.when(httpServletRequest.getAttribute(att2)).thenReturn(att2Value); + when(httpServletRequest.getHeaderNames()).thenReturn(headers); + Mockito.when(httpServletRequest.getHeader(h1)).thenReturn(h1Value); + Mockito.when(httpServletRequest.getHeader(h2)).thenReturn(h2Value); + + when(session.getAttributeNames()).thenReturn(sessionAttributes); + when(session.getAttribute(sa1)).thenReturn(sa1Value); + when(session.getAttribute(sa2)).thenReturn(sa2Value); + Mockito.when(httpServletRequest.getSession(false)).thenReturn(session); + + // Object servlet = (Object) new MyTestServlet(); + Object[] parameters = new Object[] { httpServletRequest }; + + httpHook.beforeBody(methodId, sensorTypeId, servlet, parameters, registeredSensorConfig); + + httpHook.firstAfterBody(methodId, sensorTypeId, servlet, parameters, result, registeredSensorConfig); + + httpHook.secondAfterBody(coreService, methodId, sensorTypeId, servlet, parameters, result, registeredSensorConfig); + + Mockito.verify(coreService).addMethodSensorData(Mockito.eq(registeredSensorTypeId), Mockito.eq(registeredMethodId), (String) Mockito.eq(null), + Mockito.argThat(new HttpTimerDataVerifier((HttpTimerData) data))); + Mockito.verifyZeroInteractions(result); + } + + @Test + public void oneRecordThatIsNotHttp() throws IdNotAvailableException { + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + Long firstCpuTimerValue = 5000L; + Long secondCpuTimerValue = 6872L; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + + when(threadMXBean.getCurrentThreadCpuTime()).thenReturn(firstCpuTimerValue).thenReturn(secondCpuTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + + Object[] parameters = new Object[] { servletRequest }; + + httpHook.beforeBody(methodId, sensorTypeId, servlet, parameters, registeredSensorConfig); + + httpHook.firstAfterBody(methodId, sensorTypeId, servlet, parameters, result, registeredSensorConfig); + + httpHook.secondAfterBody(coreService, methodId, sensorTypeId, servlet, parameters, result, registeredSensorConfig); + + // Data must not be pushed! + Mockito.verifyNoMoreInteractions(coreService); + Mockito.verifyZeroInteractions(result); + } + + @Test + public void twoInvocationsAfterEachOther() throws IdNotAvailableException { + // Idea: Is it also working for two invocations after each other. Is the marker reset + // correctly? + + // First invocation: + // a) has no http + // b) has http + + // Seconf invocation: + // a) has http + // b) has no http + + // initialize the ids + long platformId = 1L; + long sensorTypeId = 3L; + long registeredSensorTypeId = 7L; + + long methodId11 = 1L; + long methodId12 = 2L; + long methodId21 = 3L; + long methodId22 = 4L; + + long registeredMethodId11 = 11L; + long registeredMethodId12 = 12L; + long registeredMethodId21 = 13L; + long registeredMethodId22 = 14L; + + Double timerS11 = 1000d; + Double timerS12 = 1500d; + Double timerE12 = 2000d; + Double timerE11 = 2500d; + + Double timerS21 = 2000d; + Double timerS22 = 2500d; + Double timerE22 = 3000d; + Double timerE21 = 3500d; + + Long cpuS11 = 11000L; + Long cpuS12 = 21500L; + Long cpuE12 = 34500L; + Long cpuE11 = 45000L; + + Long cpuS21 = 52000L; + Long cpuS22 = 62500L; + Long cpuE22 = 73500L; + Long cpuE21 = 84000L; + + // The second one should have the results! + MethodSensorData data1 = new HttpTimerData(null, platformId, registeredSensorTypeId, registeredMethodId12); + MethodSensorData data2 = new HttpTimerData(null, platformId, registeredSensorTypeId, registeredMethodId21); + + when(timer.getCurrentTime()).thenReturn(timerS11).thenReturn(timerS12).thenReturn(timerE12).thenReturn(timerE11).thenReturn(timerS21).thenReturn(timerS22).thenReturn(timerE22) + .thenReturn(timerE21); + when(threadMXBean.getCurrentThreadCpuTime()).thenReturn(cpuS11).thenReturn(cpuS12).thenReturn(cpuE12).thenReturn(cpuE11).thenReturn(cpuS21).thenReturn(cpuS22).thenReturn(cpuE22) + .thenReturn(cpuE21); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId11)).thenReturn(registeredMethodId11); + when(idManager.getRegisteredMethodId(methodId12)).thenReturn(registeredMethodId12); + when(idManager.getRegisteredMethodId(methodId21)).thenReturn(registeredMethodId21); + when(idManager.getRegisteredMethodId(methodId22)).thenReturn(registeredMethodId22); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + + Object[] parametersNoHttp = new Object[] { servletRequest }; + Object[] parametersHttp = new Object[] { httpServletRequest }; + + httpHook.beforeBody(methodId11, sensorTypeId, servlet, parametersNoHttp, registeredSensorConfig); + httpHook.beforeBody(methodId12, sensorTypeId, servlet, parametersHttp, registeredSensorConfig); + + httpHook.firstAfterBody(methodId12, sensorTypeId, servlet, parametersHttp, result, registeredSensorConfig); + httpHook.secondAfterBody(coreService, methodId12, sensorTypeId, servlet, parametersHttp, result, registeredSensorConfig); + + httpHook.firstAfterBody(methodId11, sensorTypeId, servlet, parametersNoHttp, result, registeredSensorConfig); + httpHook.secondAfterBody(coreService, methodId11, sensorTypeId, servlet, parametersNoHttp, result, registeredSensorConfig); + + Mockito.verify(coreService).addMethodSensorData(Mockito.eq(registeredSensorTypeId), Mockito.eq(registeredMethodId12), (String) Mockito.eq(null), + Mockito.argThat(new HttpTimerDataVerifier((HttpTimerData) data1))); + + httpHook.beforeBody(methodId21, sensorTypeId, servlet, parametersHttp, registeredSensorConfig); + httpHook.beforeBody(methodId22, sensorTypeId, servlet, parametersNoHttp, registeredSensorConfig); + + httpHook.firstAfterBody(methodId22, sensorTypeId, servlet, parametersNoHttp, result, registeredSensorConfig); + httpHook.secondAfterBody(coreService, methodId22, sensorTypeId, servlet, parametersNoHttp, result, registeredSensorConfig); + + httpHook.firstAfterBody(methodId21, sensorTypeId, servlet, parametersHttp, result, registeredSensorConfig); + httpHook.secondAfterBody(coreService, methodId21, sensorTypeId, servlet, parametersHttp, result, registeredSensorConfig); + + Mockito.verify(coreService).addMethodSensorData(Mockito.eq(registeredSensorTypeId), Mockito.eq(registeredMethodId21), (String) Mockito.eq(null), + Mockito.argThat(new HttpTimerDataVerifier((HttpTimerData) data2))); + + // ensure that there are no exceptions (like "NoSuchElement" which means that before or + // after did not push a timer object) + + // No other data must not be pushed! + Mockito.verifyNoMoreInteractions(coreService); + + Mockito.verifyZeroInteractions(result); + + } + + @Test + public void multipleRecordsWithHttp() throws IdNotAvailableException { + // Idea: + // 1.element -> not http -> no data + // 2.element -> http -> measurement + // 3.element -> not http -> no data + // 4.element -> http -> no data (already have a measurement) + + // initialize the ids + long platformId = 1L; + long sensorTypeId = 3L; + long registeredSensorTypeId = 7L; + + long methodId1 = 1L; + long methodId2 = 2L; + long methodId3 = 3L; + long methodId4 = 4L; + + long registeredMethodId1 = 11L; + long registeredMethodId2 = 12L; + long registeredMethodId3 = 13L; + long registeredMethodId4 = 14L; + + Double timerS1 = 1000d; + Double timerS2 = 1500d; + Double timerS3 = 2000d; + Double timerS4 = 2500d; + Double timerE4 = 3500d; + Double timerE3 = 4000d; + Double timerE2 = 4500d; + Double timerE1 = 5000d; + + Long cpuS1 = 11000L; + Long cpuS2 = 21500L; + Long cpuS3 = 32000L; + Long cpuS4 = 42500L; + Long cpuE4 = 53500L; + Long cpuE3 = 64000L; + Long cpuE2 = 74500L; + Long cpuE1 = 85000L; + + // The second one should have the results! + MethodSensorData data = new HttpTimerData(null, platformId, registeredSensorTypeId, registeredMethodId2); + + when(timer.getCurrentTime()).thenReturn(timerS1).thenReturn(timerS2).thenReturn(timerS3).thenReturn(timerS4).thenReturn(timerE4).thenReturn(timerE3).thenReturn(timerE2).thenReturn(timerE1); + when(threadMXBean.getCurrentThreadCpuTime()).thenReturn(cpuS1).thenReturn(cpuS2).thenReturn(cpuS3).thenReturn(cpuS4).thenReturn(cpuE4).thenReturn(cpuE3).thenReturn(cpuE2).thenReturn(cpuE1); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId1)).thenReturn(registeredMethodId1); + when(idManager.getRegisteredMethodId(methodId2)).thenReturn(registeredMethodId2); + when(idManager.getRegisteredMethodId(methodId3)).thenReturn(registeredMethodId3); + when(idManager.getRegisteredMethodId(methodId4)).thenReturn(registeredMethodId4); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + + Object[] parameters1 = new Object[] { servletRequest }; + Object[] parameters2 = new Object[] { httpServletRequest }; + Object[] parameters3 = new Object[] { "Ich bin ein String und keine http information" }; + Object[] parameters4 = new Object[] { httpServletRequest }; + + httpHook.beforeBody(methodId1, sensorTypeId, servlet, parameters1, registeredSensorConfig); + httpHook.beforeBody(methodId2, sensorTypeId, servlet, parameters2, registeredSensorConfig); + httpHook.beforeBody(methodId3, sensorTypeId, servlet, parameters3, registeredSensorConfig); + httpHook.beforeBody(methodId4, sensorTypeId, servlet, parameters4, registeredSensorConfig); + + httpHook.firstAfterBody(methodId4, sensorTypeId, servlet, parameters4, result, registeredSensorConfig); + httpHook.secondAfterBody(coreService, methodId4, sensorTypeId, servlet, parameters4, result, registeredSensorConfig); + + httpHook.firstAfterBody(methodId3, sensorTypeId, servlet, parameters3, result, registeredSensorConfig); + httpHook.secondAfterBody(coreService, methodId3, sensorTypeId, servlet, parameters3, result, registeredSensorConfig); + + httpHook.firstAfterBody(methodId2, sensorTypeId, servlet, parameters2, result, registeredSensorConfig); + httpHook.secondAfterBody(coreService, methodId2, sensorTypeId, servlet, parameters2, result, registeredSensorConfig); + + httpHook.firstAfterBody(methodId1, sensorTypeId, servlet, parameters1, result, registeredSensorConfig); + httpHook.secondAfterBody(coreService, methodId1, sensorTypeId, servlet, parameters1, result, registeredSensorConfig); + + Mockito.verify(coreService).addMethodSensorData(Mockito.eq(registeredSensorTypeId), Mockito.eq(registeredMethodId2), (String) Mockito.eq(null), + Mockito.argThat(new HttpTimerDataVerifier((HttpTimerData) data))); + + // No other data must not be pushed! + Mockito.verifyNoMoreInteractions(coreService); + + Mockito.verifyZeroInteractions(result); + } + + /** + * Inner class used to verify the contents of PlainTimerData objects. + */ + private static class HttpTimerDataVerifier extends ArgumentMatcher { + private final HttpTimerData data; + + public HttpTimerDataVerifier(HttpTimerData data) { + this.data = data; + } + + @Override + public boolean matches(Object object) { + if (!HttpTimerData.class.isInstance(object)) { + return false; + } + HttpTimerData other = (HttpTimerData) object; + + assertThat(data.getUri(), is(equalTo(other.getUri()))); + assertThat(data.getRequestMethod(), is(equalTo(other.getRequestMethod()))); + assertThat(data.getAttributes(), is(equalTo(other.getAttributes()))); + assertThat(data.getHeaders(), is(equalTo(other.getHeaders()))); + assertThat(data.getSessionAttributes(), is(equalTo(other.getSessionAttributes()))); + assertThat(data.getParameters(), is(equalTo(other.getParameters()))); + + return true; + } + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/method/http/HttpRequestParameterExtractorTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/method/http/HttpRequestParameterExtractorTest.java new file mode 100644 index 000000000..d1323b7d2 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/method/http/HttpRequestParameterExtractorTest.java @@ -0,0 +1,304 @@ +package info.novatec.inspectit.agent.sensor.method.http; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.util.StringConstraint; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.apache.commons.collections.MapUtils; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class HttpRequestParameterExtractorTest extends AbstractLogSupport { + + private HttpRequestParameterExtractor extractor; + + @Mock + private HttpServletRequest httpServletRequest; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + extractor = new HttpRequestParameterExtractor(new StringConstraint(Collections. singletonMap("stringLength", "20"))); + } + + @Test + public void readParameters() { + final String param1 = "p1"; + final String param2 = "p2"; + final String param1VReal = "I am a really long string that should be cropped in an meaningful way."; + final String param2VReal1 = "value5"; + final String param2VReal2 = "value6"; + final String[] param1V = new String[] { param1VReal }; + final String[] param2V = new String[] { param2VReal1, param2VReal2 }; + final Map parameterMap = new HashMap(); + MapUtils.putAll(parameterMap, new Object[][] { { param1, param1V }, { param2, param2V } }); + + when(httpServletRequest.getParameterMap()).thenReturn(parameterMap); + + Map result = extractor.getParameterMap(httpServletRequest.getClass(), httpServletRequest); + + assertThat(result.size(), is(parameterMap.size())); + assertThat(result, hasKey(param1)); + assertThat(result, hasKey(param2)); + assertThat("Value should be cropped!", result.get(param1), is(not(param1V))); + assertThat(result.get(param2), is(param2V)); + } + + @Test + public void readParametersNull() { + Map result = extractor.getParameterMap(httpServletRequest.getClass(), httpServletRequest); + + assertThat(result, is(nullValue())); + } + + @Test + public void readHeaders() { + final String h1 = "h1"; + final String h2 = "h2"; + final String h1Value = "hValue1"; + final String h2Value = "hValue2"; + final Vector headersList = new Vector(); + Collections.addAll(headersList, h1, h2); + + final Enumeration headers = headersList.elements(); + + when(httpServletRequest.getHeaderNames()).thenReturn(headers); + when(httpServletRequest.getHeader(h1)).thenReturn(h1Value); + when(httpServletRequest.getHeader(h2)).thenReturn(h2Value); + + Map result = extractor.getHeaders(httpServletRequest.getClass(), httpServletRequest); + + Map expected = new HashMap(); + MapUtils.putAll(expected, new Object[][] { { h1, h1Value }, { h2, h2Value } }); + assertThat(result, is(equalTo(expected))); + + // We only create a new instance of the element if we need to change it (e.g. crop) + assertThat("No new instances", result.get(h1) == h1Value); + assertThat("No new instances", result.get(h2) == h2Value); + } + + @Test + public void readHeadersCrop() { + final String h1 = "h1"; + final String h2 = "h2"; + // this will be cropped! + final String h1Value = "hValue1aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + final String h2Value = "hValue2"; + final Vector headersList = new Vector(); + Collections.addAll(headersList, h1, h2); + + final Enumeration headers = headersList.elements(); + + when(httpServletRequest.getHeaderNames()).thenReturn(headers); + when(httpServletRequest.getHeader(h1)).thenReturn(h1Value); + when(httpServletRequest.getHeader(h2)).thenReturn(h2Value); + + Map result = extractor.getHeaders(httpServletRequest.getClass(), httpServletRequest); + + assertThat(result.size(), is(2)); + + // We only create a new instance of the element if we need to change it (e.g. crop) + assertThat("No new instances", result.get(h1) != h1Value); + assertThat("No new instances", result.get(h2) == h2Value); + } + + @Test + public void readHeadersNull() { + Map result = extractor.getHeaders(httpServletRequest.getClass(), httpServletRequest); + + assertThat(result, is(nullValue())); + } + + @Test + public void readAttributes() { + final String att1 = "a1"; + final String att2 = "a2"; + final String att1Value = "aValue1"; + final String att2Value = "aValue2"; + final Vector attributesList = new Vector(); + Collections.addAll(attributesList, att1, att2); + + final Enumeration attributes = attributesList.elements(); + + when(httpServletRequest.getAttributeNames()).thenReturn(attributes); + + when(httpServletRequest.getAttribute(att1)).thenReturn(att1Value); + when(httpServletRequest.getAttribute(att2)).thenReturn(att2Value); + + Map result = extractor.getAttributes(httpServletRequest.getClass(), httpServletRequest); + + Map expected = new HashMap(); + MapUtils.putAll(expected, new Object[][] { { att1, att1Value }, { att2, att2Value } }); + + assertThat(result, is(equalTo(expected))); + // We only create a new instance of the element if we need to change it (e.g. crop) + assertThat("No new instances", result.get(att1) == att1Value); + assertThat("No new instances", result.get(att2) == att2Value); + } + + @Test + public void readArrayAttributes() { + final String att1 = "a1"; + final String att2 = "a2"; + final String[] att1Value = { "attValue1", "attValue2", "attValue3" }; + final String[] att2Value = { "a1", "a2", "a3" }; + final Vector attributesList = new Vector(); + Collections.addAll(attributesList, att1, att2); + final Enumeration attributes = attributesList.elements(); + + when(httpServletRequest.getAttributeNames()).thenReturn(attributes); + when(httpServletRequest.getAttribute(att1)).thenReturn(att1Value); + when(httpServletRequest.getAttribute(att2)).thenReturn(att2Value); + + Map result = extractor.getAttributes(httpServletRequest.getClass(), httpServletRequest); + + final String extractedAttribute1Value = "[attValue1, attValue2, attValue3]".substring(0, 20) + "..."; + final String extractedAttribute2Value = "[a1, a2, a3]"; + Map expected = new HashMap(); + MapUtils.putAll(expected, new Object[][] { { att1, extractedAttribute1Value }, { att2, extractedAttribute2Value } }); + + assertThat(result, is(equalTo(expected))); + } + + @Test + public void readArrayAttributesPrimitivesInteger() { + final String att1 = "a1"; + final String att2 = "a2"; + final int[] att1Value = { 1, 2, 3 }; + final int[] att2Value = { 2, 3, 4 }; + final Vector attributesList = new Vector(); + Collections.addAll(attributesList, att1, att2); + + final Enumeration attributes = attributesList.elements(); + + when(httpServletRequest.getAttributeNames()).thenReturn(attributes); + when(httpServletRequest.getAttribute(att1)).thenReturn(att1Value); + when(httpServletRequest.getAttribute(att2)).thenReturn(att2Value); + + Map result = extractor.getAttributes(httpServletRequest.getClass(), httpServletRequest); + + final String extractedAttribute1Value = "[1, 2, 3]"; + final String extractedAttribute2Value = "[2, 3, 4]"; + Map expected = new HashMap(); + MapUtils.putAll(expected, new Object[][] { { att1, extractedAttribute1Value }, { att2, extractedAttribute2Value } }); + + assertThat(result, is(equalTo(expected))); + } + + @Test + public void readAttributesNull() { + Map result = extractor.getAttributes(httpServletRequest.getClass(), httpServletRequest); + + assertThat(result, is(nullValue())); + } + + @Test + public void readRequestUri() { + final String uri = "URI"; + when(httpServletRequest.getRequestURI()).thenReturn(uri); + + String result = extractor.getRequestUri(httpServletRequest.getClass(), httpServletRequest); + assertThat(result, is(equalTo(uri))); + assertThat("Same instances", uri == result); + } + + @Test + public void readRequestUriNull() { + String result = extractor.getRequestUri(httpServletRequest.getClass(), httpServletRequest); + + assertThat(result, is(HttpTimerData.UNDEFINED)); + } + + @Test + public void readRequestMethod() { + final String method = "GET"; + when(httpServletRequest.getMethod()).thenReturn(method); + + String result = extractor.getRequestMethod(httpServletRequest.getClass(), httpServletRequest); + assertThat(result, is(equalTo(method))); + assertThat("Same istances", method == result); + } + + @Test + public void readRequestMethodNull() { + String result = extractor.getRequestMethod(httpServletRequest.getClass(), httpServletRequest); + + assertThat(result, is(HttpTimerData.UNDEFINED)); + } + + @Test + public void sessionAttributesWithNoSession() { + when(httpServletRequest.getSession(false)).thenReturn(null); + + Map result = extractor.getSessionAttributes(httpServletRequest.getClass(), httpServletRequest); + assertThat(result, is(nullValue())); + } + + @Test + public void sessionAttributesWithSession() { + + final String sa1 = "sa1"; + final String sa2 = "sa2"; + final String sa1Value = "saValue1"; + final String sa2Value = "saValue2"; + final Vector sessionAttributesList = new Vector(); + Collections.addAll(sessionAttributesList, sa1, sa2); + final Enumeration sessionAttributes = sessionAttributesList.elements(); + + HttpSession session = Mockito.mock(HttpSession.class); + when(session.getAttributeNames()).thenReturn(sessionAttributes); + when(session.getAttribute(sa1)).thenReturn(sa1Value); + when(session.getAttribute(sa2)).thenReturn(sa2Value); + when(httpServletRequest.getSession(false)).thenReturn(session); + + Map result = extractor.getSessionAttributes(httpServletRequest.getClass(), httpServletRequest); + Map expected = new HashMap(); + MapUtils.putAll(expected, new Object[][] { { sa1, sa1Value }, { sa2, sa2Value } }); + + assertThat(result, is(equalTo(expected))); + } + + @Test + public void sessionArrayAttributesWithSession() { + final String sa1 = "sa1"; + final String sa2 = "sa2"; + final String[] sa1Value = { "saValue1", "saValue2", "saValue3" }; + final String[] sa2Value = { "s1", "s2", "s3" }; + final Vector sessionAttributesList = new Vector(); + Collections.addAll(sessionAttributesList, sa1, sa2); + final Enumeration sessionAttributes = sessionAttributesList.elements(); + + HttpSession session = Mockito.mock(HttpSession.class); + when(session.getAttributeNames()).thenReturn(sessionAttributes); + when(session.getAttribute(sa1)).thenReturn(sa1Value); + when(session.getAttribute(sa2)).thenReturn(sa2Value); + when(httpServletRequest.getSession(false)).thenReturn(session); + + Map result = extractor.getSessionAttributes(httpServletRequest.getClass(), httpServletRequest); + + final String extractedSa1Value = "[saValue1, saValue2, saValue3]".substring(0, 20) + "..."; + final String extractedSa2Value = "[s1, s2, s3]"; + Map expected = new HashMap(); + MapUtils.putAll(expected, new Object[][] { { sa1, extractedSa1Value }, { sa2, extractedSa2Value } }); + + assertThat(result, is(equalTo(expected))); + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/method/invocationsequence/InvocationSequenceHookTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/method/invocationsequence/InvocationSequenceHookTest.java new file mode 100644 index 000000000..d876747e2 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/method/invocationsequence/InvocationSequenceHookTest.java @@ -0,0 +1,223 @@ +package info.novatec.inspectit.agent.sensor.method.invocationsequence; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.config.IPropertyAccessor; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.core.impl.CoreService; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.util.Timer; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Testing the {@link InvocationSequenceHook}. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("PMD") +public class InvocationSequenceHookTest extends AbstractLogSupport { + + /** + * Class under test. + */ + private InvocationSequenceHook invocationSequenceHook; + + @Mock + private Timer timer; + + @Mock + private IIdManager idManager; + + @Mock + private IPropertyAccessor propertyAccessor; + + @Mock + private RegisteredSensorConfig rsc; + + @Mock + private CoreService coreService; + + @BeforeMethod + public void init() { + MockitoAnnotations.initMocks(this); + invocationSequenceHook = new InvocationSequenceHook(timer, idManager, propertyAccessor, Collections. emptyMap(), false); + } + + /** + * Tests that the correct time and ids will be set on the invocation. + * + * @throws IdNotAvailableException + */ + @Test + public void startEndInvocationWithDataSaving() throws IdNotAvailableException { + long platformId = 1L; + long methodId = 3L; + long registeredMethodId = 13L; + long sensorTypeId = 11L; + long registeredSensorTypeId = 7L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + + double firstTimerValue = 1000.0d; + double secondTimerValue = 1323.0d; + when(timer.getCurrentTime()).thenReturn(firstTimerValue, secondTimerValue); + + invocationSequenceHook.beforeBody(methodId, sensorTypeId, object, parameters, rsc); + + // save two objects + TimerData timerData = new TimerData(); + SqlStatementData sqlStatementData = new SqlStatementData(); + invocationSequenceHook.addMethodSensorData(0, 0, "", timerData); + invocationSequenceHook.addMethodSensorData(0, 0, "", sqlStatementData); + invocationSequenceHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, rsc); + invocationSequenceHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, rsc); + + verify(timer, times(2)).getCurrentTime(); + ArgumentCaptor captor = ArgumentCaptor.forClass(InvocationSequenceData.class); + verify(coreService, times(1)).addMethodSensorData(eq(sensorTypeId), eq(methodId), Mockito. anyObject(), captor.capture()); + + InvocationSequenceData invocation = captor.getValue(); + assertThat(invocation.getPlatformIdent(), is(platformId)); + assertThat(invocation.getMethodIdent(), is(registeredMethodId)); + assertThat(invocation.getSensorTypeIdent(), is(registeredSensorTypeId)); + assertThat(invocation.getDuration(), is(secondTimerValue - firstTimerValue)); + assertThat(invocation.getNestedSequences(), is(empty())); + assertThat(invocation.getChildCount(), is(0L)); + assertThat(invocation.getTimerData(), is(timerData)); + assertThat(invocation.getSqlStatementData(), is(sqlStatementData)); + } + + /** + * Tests that the invocation and child will have correct times and ids. + * + * @throws IdNotAvailableException + */ + @Test + public void twoInvocationsParentChild() throws IdNotAvailableException { + long platformId = 1L; + long methodId1 = 3L; + long registeredMethodId1 = 13L; + long sensorTypeId = 11L; + long registeredSensorTypeId = 7L; + long methodId2 = 23L; + long registeredMethodId2 = 27L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + when(idManager.getRegisteredMethodId(methodId1)).thenReturn(registeredMethodId1); + when(idManager.getRegisteredMethodId(methodId2)).thenReturn(registeredMethodId2); + + double firstTimerValue = 1000.0d; + double secondTimerValue = 1323.0d; + double thirdTimerValue = 1881.0d; + double fourthTimerValue = 2562.0d; + when(timer.getCurrentTime()).thenReturn(firstTimerValue, secondTimerValue, thirdTimerValue, fourthTimerValue); + + invocationSequenceHook.beforeBody(methodId1, sensorTypeId, object, parameters, rsc); + invocationSequenceHook.beforeBody(methodId2, sensorTypeId, object, parameters, rsc); + invocationSequenceHook.firstAfterBody(methodId2, sensorTypeId, object, parameters, result, rsc); + invocationSequenceHook.secondAfterBody(coreService, methodId2, sensorTypeId, object, parameters, result, rsc); + invocationSequenceHook.firstAfterBody(methodId1, sensorTypeId, object, parameters, result, rsc); + invocationSequenceHook.secondAfterBody(coreService, methodId1, sensorTypeId, object, parameters, result, rsc); + + verify(timer, times(4)).getCurrentTime(); + ArgumentCaptor captor = ArgumentCaptor.forClass(InvocationSequenceData.class); + verify(coreService, times(1)).addMethodSensorData(eq(sensorTypeId), eq(methodId1), Mockito. anyObject(), captor.capture()); + + InvocationSequenceData invocation = captor.getValue(); + assertThat(invocation.getPlatformIdent(), is(platformId)); + assertThat(invocation.getMethodIdent(), is(registeredMethodId1)); + assertThat(invocation.getSensorTypeIdent(), is(registeredSensorTypeId)); + assertThat(invocation.getDuration(), is(fourthTimerValue - firstTimerValue)); + assertThat(invocation.getNestedSequences(), hasSize(1)); + assertThat(invocation.getChildCount(), is(1L)); + InvocationSequenceData child = invocation.getNestedSequences().iterator().next(); + assertThat(child.getPlatformIdent(), is(platformId)); + assertThat(child.getMethodIdent(), is(registeredMethodId2)); + assertThat(child.getSensorTypeIdent(), is(registeredSensorTypeId)); + assertThat(child.getDuration(), is(thirdTimerValue - secondTimerValue)); + assertThat(child.getNestedSequences(), is(empty())); + assertThat(child.getParentSequence(), is(invocation)); + assertThat(child.getChildCount(), is(0L)); + } + + /** + * Tests that invocation will not be saved if the duration is below min duration specified in + * the rsc settings. + * + * @throws IdNotAvailableException + */ + @Test + public void minDuration() throws IdNotAvailableException { + long platformId = 1L; + long methodId = 3L; + long registeredMethodId = 13L; + long sensorTypeId = 11L; + long registeredSensorTypeId = 7L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + + double firstTimerValue = 1000.0d; + double secondTimerValue = 1200.0d; + String minDuration = "201"; + when(timer.getCurrentTime()).thenReturn(firstTimerValue, secondTimerValue); + Map map = new HashMap(); + map.put("minduration", minDuration); + when(rsc.getSettings()).thenReturn(map); + + invocationSequenceHook.beforeBody(methodId, sensorTypeId, object, parameters, rsc); + invocationSequenceHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, rsc); + invocationSequenceHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, rsc); + + verify(timer, times(2)).getCurrentTime(); + verifyZeroInteractions(coreService); + + secondTimerValue = 1202.0d; + when(timer.getCurrentTime()).thenReturn(firstTimerValue, secondTimerValue); + + invocationSequenceHook.beforeBody(methodId, sensorTypeId, object, parameters, rsc); + invocationSequenceHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, rsc); + invocationSequenceHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, rsc); + + verify(timer, times(4)).getCurrentTime(); + verify(coreService, times(1)).addMethodSensorData(eq(sensorTypeId), eq(methodId), Mockito. anyObject(), Mockito. anyObject()); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataExtractorTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataExtractorTest.java new file mode 100644 index 000000000..bf1d920d2 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataExtractorTest.java @@ -0,0 +1,68 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import info.novatec.inspectit.agent.sensor.method.jdbc.ConnectionMetaDataStorage.ConnectionMetaData; +import info.novatec.inspectit.agent.sensor.method.jdbc.ConnectionMetaDataStorage.ConnectionMetaDataExtractor; +import info.novatec.inspectit.agent.sensor.method.jdbc.ConnectionMetaDataStorage.JDBCUrlExtractor; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; + +import org.mockito.Mockito; +import org.slf4j.Logger; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class ConnectionMetaDataExtractorTest { + + private ConnectionMetaDataExtractor extractor; + + private static final String URL = "url"; + + @SuppressWarnings("static-access") + @BeforeMethod + public void init() { + extractor = new ConnectionMetaDataExtractor(); + JDBCUrlExtractor jdbExtractor = Mockito.mock(JDBCUrlExtractor.class); + Mockito.when(jdbExtractor.extractURLfromJDBCURL(Mockito.anyString())).thenReturn(URL); + extractor.urlExtractor = jdbExtractor; + } + + @Test + public void extractInformation() throws SQLException { + Connection mockedConnection = Mockito.mock(Connection.class); + DatabaseMetaData mockedMetaData = Mockito.mock(DatabaseMetaData.class); + + String url = "url"; + String version = "version"; + String name = "name"; + + Mockito.when(mockedConnection.getMetaData()).thenReturn(mockedMetaData); + Mockito.when(mockedMetaData.getURL()).thenReturn(url); + Mockito.when(mockedMetaData.getDatabaseProductName()).thenReturn(name); + Mockito.when(mockedMetaData.getDatabaseProductVersion()).thenReturn(version); + + ConnectionMetaData data = extractor.parse(mockedConnection); + + assertThat(data.product, is(name)); + assertThat(data.version, is(version)); + assertThat(data.url, is(url)); + } + + @SuppressWarnings("static-access") + @Test + public void extractInformationForNullConnectionResultsInNoErrorButAWarningMessage() { + Logger mockedLogger = Mockito.mock(Logger.class); + extractor.logger = mockedLogger; + + ConnectionMetaData data = extractor.parse(null); + + assertThat(data, is(not(nullValue()))); + Mockito.verify(mockedLogger).warn(Mockito.anyString()); + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataHookTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataHookTest.java new file mode 100644 index 000000000..9f41227b0 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataHookTest.java @@ -0,0 +1,60 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.impl.IdManager; +import info.novatec.inspectit.util.Timer; + +import java.sql.Connection; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class ConnectionMetaDataHookTest extends AbstractLogSupport { + + ConnectionMetaDataHook hook; + + @Mock + ConnectionMetaDataStorage storage; + + @Mock + Connection myConnection; + + @Mock + private Timer timer; + + @Mock + private IdManager idManager; + + @Mock + private ICoreService coreService; + + @Mock + private RegisteredSensorConfig registeredSensorConfig; + + @BeforeMethod + public void init() { + hook = new ConnectionMetaDataHook(storage); + } + + @Test + public void normalRunThrough() { + // set up data + long methodId = 3L; + long registeredMethodId = 13L; + long sensorTypeId = 11L; + long registeredSensorTypeId = 7L; + Object[] parameters = new Object[0]; + + // dispatch the beforeMethod + hook.beforeConstructor(methodId, sensorTypeId, parameters, registeredSensorConfig); + Mockito.verifyZeroInteractions(storage); + + hook.afterConstructor(coreService, registeredMethodId, registeredSensorTypeId, myConnection, parameters, registeredSensorConfig); + Mockito.verify(storage, Mockito.timeout(1)).add(myConnection); + Mockito.verifyNoMoreInteractions(storage); + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataStorageTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataStorageTest.java new file mode 100644 index 000000000..83045cba2 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/ConnectionMetaDataStorageTest.java @@ -0,0 +1,59 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import info.novatec.inspectit.agent.sensor.method.jdbc.ConnectionMetaDataStorage.ConnectionMetaData; +import info.novatec.inspectit.agent.sensor.method.jdbc.ConnectionMetaDataStorage.ConnectionMetaDataExtractor; + +import org.mockito.Mockito; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class ConnectionMetaDataStorageTest { + + private ConnectionMetaDataStorage storage; + + @BeforeMethod + public void init() { + storage = new ConnectionMetaDataStorage(); + + } + + @Test + public void addNullConnectionInstance() { + storage.add(null); + assertThat((int) storage.storage.size(), is(0)); + } + + @Test + public void getNotExisting() { + ConnectionMetaData data = storage.get("String"); + assertThat(data, is(nullValue())); + } + + @Test + public void getNull() { + ConnectionMetaData data = storage.get(null); + assertThat(data, is(nullValue())); + } + + @Test + public void addAndGetConnection() { + ConnectionMetaDataExtractor extractor = Mockito.mock(ConnectionMetaDataExtractor.class); + ConnectionMetaData data = Mockito.mock(ConnectionMetaData.class); + Mockito.when(extractor.parse(Mockito.anyObject())).thenReturn(data); + storage.dataExtractor = extractor; + Object connectionObject = ""; // note that we can pass this as we mocked the data + // extraction. + + storage.add(connectionObject); + assertThat((int) storage.storage.size(), is(1)); + + storage.add(connectionObject); + assertThat((int) storage.storage.size(), is(1)); + + assertThat(storage.get(connectionObject), is(data)); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/JDBCUrlExtractorTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/JDBCUrlExtractorTest.java new file mode 100644 index 000000000..99a03a8de --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/JDBCUrlExtractorTest.java @@ -0,0 +1,47 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import info.novatec.inspectit.agent.sensor.method.jdbc.ConnectionMetaDataStorage.JDBCUrlExtractor; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class JDBCUrlExtractorTest { + + private JDBCUrlExtractor extractor; + + @BeforeTest + public void init() { + extractor = new JDBCUrlExtractor(); + } + + // jdbc:sqlserver://[serverName[\instanceName][:portNumber]][;property=value[;property=value]] + // * jdbc:db2://:/ + // * jdbc:h2:../../database/database/dvdstore22 + + @DataProvider + public Object[][] differentVendors() { + return new Object[][] { { "jdbc:h2:../../database/database/dvdstore22", "../../database/database/dvdstore22" }, + { "jdbc:h2:myh2server.mycompany.de:7000/dvdstore22", "myh2server.mycompany.de:7000/dvdstore22" }, { "jdbc:db2://mydb2instance:8000/mydatabase", "mydb2instance:8000/mydatabase" }, + { "jdbc:sqlserver://sqlserver\\myinstance:5000;prop1=1;prop2=2", "sqlserver\\myinstance:5000" }, { "jdbc:oracle:thin:@//myhost:1521/orcl", "myhost:1521/orcl" }, + { "jdbc:oracle:thin:@myhost:1521:orcl", "myhost:1521:orcl" }, { "jdbc:oracle:oci:@myhost:1521:orcl", "myhost:1521:orcl" }, + { "jdbc:mysql://localhost/test?user=monty&password=greatsqldb", "localhost/test" }, { "jdbc:postgresql:database", "database" }, + { "jdbc:postgresql://localhost/test", "localhost/test" }, { "jdbc:postgresql://localhost:5000/test", "localhost:5000/test" }, { "jdbc:odbc:HY_FLAT", "HY_FLAT" }, + { "jdbc:odbc:SQL_SERVER;user=sa;password=HerongYang", "SQL_SERVER" } }; + } + + @Test(dataProvider = "differentVendors") + public void extractFromFormat(String input, String expected) { + String result = extractor.extractURLfromJDBCURL(input); + assertThat(result, is(expected)); + } + + @Test + public void invalidFormat() { + String invalid = "this is just an invalid format"; + String result = extractor.extractURLfromJDBCURL(invalid); + assertThat(result, is(invalid)); + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementHookTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementHookTest.java new file mode 100644 index 000000000..1bc1ce9f5 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementHookTest.java @@ -0,0 +1,76 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.core.impl.IdManager; +import info.novatec.inspectit.util.Timer; + +import java.util.Map; +import java.util.NoSuchElementException; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.slf4j.Logger; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class PreparedStatementHookTest extends AbstractLogSupport { + + @Mock + private Timer timer; + + @Mock + private IdManager idManager; + + @Mock + private StatementStorage statementStorage; + + @Mock + private ConnectionMetaDataStorage connectionMetaDataStorage; + + @Mock + private NoSuchElementException myNoSuchElementException; + + @Mock + private StatementReflectionCache statementReflectionCache; + + @Mock + private Map parameter; + + @Mock + private Logger log; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + Mockito.doThrow(myNoSuchElementException).when(statementStorage).addPreparedStatement(Mockito.anyObject()); + } + + @Test + public void exceptionLoggingTest() { + PreparedStatementHook hook = new PreparedStatementHook(timer, idManager, statementStorage, connectionMetaDataStorage, statementReflectionCache, parameter); + hook.log = log; + + // Throwing the same exception a few times... (as statement storage always raises the + // exception) + hook.afterConstructor(null, 1, 10, "someObject", null, null); + hook.afterConstructor(null, 1, 10, "someObject", null, null); + hook.afterConstructor(null, 1, 10, "someObject", null, null); + hook.afterConstructor(null, 1, 10, "someObject", null, null); + hook.afterConstructor(null, 1, 10, "someObject", null, null); + hook.afterConstructor(null, 1, 10, "someObject", null, null); + + // ... we still should get only one printing out of the exception + Mockito.verify(log, Mockito.times(1)).info(Mockito.anyString(), Mockito.eq(myNoSuchElementException)); + + // ... if we have a different Statement (meaning different methodId) it should + // print it out again + hook.afterConstructor(null, 2, 10, "someObject", null, null); + hook.afterConstructor(null, 2, 10, "someObject", null, null); + hook.afterConstructor(null, 2, 10, "someObject", null, null); + hook.afterConstructor(null, 2, 10, "someObject", null, null); + hook.afterConstructor(null, 2, 10, "someObject", null, null); + + Mockito.verify(log, Mockito.times(2)).info(Mockito.anyString(), Mockito.eq(myNoSuchElementException)); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementParameterHookTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementParameterHookTest.java new file mode 100644 index 000000000..398d9665a --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/PreparedStatementParameterHookTest.java @@ -0,0 +1,205 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; + +import java.util.List; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Tests the {@link PreparedStatementParameterHook}. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("PMD") +public class PreparedStatementParameterHookTest extends AbstractLogSupport { + + /** + * Testing class. + */ + private PreparedStatementParameterHook preparedStatementParameterHook; + + @Mock + private StatementStorage statementStorage; + + @Mock + private RegisteredSensorConfig rsc; + + @Mock + private ICoreService coreService; + + /** + * Initializes the test class. + */ + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + preparedStatementParameterHook = new PreparedStatementParameterHook(statementStorage); + } + + /** + * Test setting of the correct index and parameter value. + */ + @SuppressWarnings("unchecked") + @Test + public void setCorrectParameterAndIndex() { + List parameterTypes = Mockito.mock(List.class); + Mockito.when(parameterTypes.size()).thenReturn(2); + Mockito.when(parameterTypes.get(0)).thenReturn("int"); + + Mockito.when(rsc.getParameterTypes()).thenReturn(parameterTypes); + + Object[] parameters = new Object[] { 1, "Value" }; + Object object = new Object(); + + preparedStatementParameterHook.beforeBody(0, 0, object, parameters, rsc); + preparedStatementParameterHook.firstAfterBody(0, 0, object, parameters, null, rsc); + preparedStatementParameterHook.secondAfterBody(coreService, 0, 0, object, parameters, null, rsc); + + Mockito.verify(statementStorage, Mockito.times(1)).addParameter(object, 0, "Value"); + Mockito.verifyNoMoreInteractions(statementStorage); + Mockito.verifyZeroInteractions(coreService); + } + + /** + * Tests that no interaction is happening when first parameter is not 'int'. + */ + @SuppressWarnings("unchecked") + @Test + public void wrongFirstParameter() { + List parameterTypes = Mockito.mock(List.class); + Mockito.when(parameterTypes.size()).thenReturn(2); + Mockito.when(parameterTypes.get(0)).thenReturn("long"); + + Mockito.when(rsc.getParameterTypes()).thenReturn(parameterTypes); + + Object[] parameters = new Object[] { 1L, "Value" }; + Object object = new Object(); + + preparedStatementParameterHook.beforeBody(0, 0, object, parameters, rsc); + preparedStatementParameterHook.firstAfterBody(0, 0, object, parameters, null, rsc); + preparedStatementParameterHook.secondAfterBody(coreService, 0, 0, object, parameters, null, rsc); + + Mockito.verifyZeroInteractions(statementStorage); + Mockito.verifyZeroInteractions(coreService); + } + + /** + * Tests arbitrary method with wrong parameter count. + */ + @SuppressWarnings("unchecked") + @Test + public void wrongMethodInstrumentation() { + List parameterTypes = Mockito.mock(List.class); + Mockito.when(parameterTypes.size()).thenReturn(1); + Mockito.when(parameterTypes.get(0)).thenReturn("int"); + + Mockito.when(rsc.getParameterTypes()).thenReturn(parameterTypes); + Mockito.when(rsc.getTargetMethodName()).thenReturn("myMethod"); + + Object[] parameters = new Object[] { 1 }; + Object object = new Object(); + + preparedStatementParameterHook.beforeBody(0, 0, object, parameters, rsc); + preparedStatementParameterHook.firstAfterBody(0, 0, object, parameters, null, rsc); + preparedStatementParameterHook.secondAfterBody(coreService, 0, 0, object, parameters, null, rsc); + + Mockito.verifyZeroInteractions(statementStorage); + Mockito.verifyZeroInteractions(coreService); + } + + /** + * Test that setNull() methods of the prepare statement will set the null as the + * parameter value. + */ + @SuppressWarnings("unchecked") + @Test + public void setNull() { + List parameterTypes = Mockito.mock(List.class); + Mockito.when(parameterTypes.size()).thenReturn(2); + Mockito.when(parameterTypes.get(0)).thenReturn("int"); + + Mockito.when(rsc.getParameterTypes()).thenReturn(parameterTypes); + Mockito.when(rsc.getTargetMethodName()).thenReturn("setNull"); + + Object[] parameters = new Object[] { 1, 2 }; + Object object = new Object(); + + preparedStatementParameterHook.beforeBody(0, 0, object, parameters, rsc); + preparedStatementParameterHook.firstAfterBody(0, 0, object, parameters, null, rsc); + preparedStatementParameterHook.secondAfterBody(coreService, 0, 0, object, parameters, null, rsc); + + Mockito.verify(statementStorage, Mockito.times(1)).addParameter(object, 0, null); + Mockito.verifyNoMoreInteractions(statementStorage); + Mockito.verifyZeroInteractions(coreService); + } + + /** + * Tests clear parameters method being invoked. + */ + @SuppressWarnings("unchecked") + @Test + public void clearParameters() { + List parameterTypes = Mockito.mock(List.class); + Mockito.when(parameterTypes.size()).thenReturn(0); + + Mockito.when(rsc.getParameterTypes()).thenReturn(parameterTypes); + Mockito.when(rsc.getTargetMethodName()).thenReturn("clearParameters"); + + Object[] parameters = new Object[0]; + Object object = new Object(); + + preparedStatementParameterHook.beforeBody(0, 0, object, parameters, rsc); + preparedStatementParameterHook.firstAfterBody(0, 0, object, parameters, null, rsc); + preparedStatementParameterHook.secondAfterBody(coreService, 0, 0, object, parameters, null, rsc); + + Mockito.verify(statementStorage, Mockito.times(1)).clearParameters(object); + Mockito.verifyNoMoreInteractions(statementStorage); + Mockito.verifyZeroInteractions(coreService); + } + + /** + * Tests that the methods with big data structures will not set the complete structure, but a + * simple marker instead. + * + * @param methodName + * Method name that holds big structure. + */ + @SuppressWarnings("unchecked") + @Test(dataProvider = "methodsWithBigDataStructures") + public void methodWithBigData(String methodName) { + List parameterTypes = Mockito.mock(List.class); + Mockito.when(parameterTypes.size()).thenReturn(2); + Mockito.when(parameterTypes.get(0)).thenReturn("int"); + + Mockito.when(rsc.getParameterTypes()).thenReturn(parameterTypes); + Mockito.when(rsc.getTargetMethodName()).thenReturn(methodName); + + Object[] parameters = new Object[] { 1, "Value" }; + Object object = new Object(); + + preparedStatementParameterHook.beforeBody(0, 0, object, parameters, rsc); + preparedStatementParameterHook.firstAfterBody(0, 0, object, parameters, null, rsc); + preparedStatementParameterHook.secondAfterBody(coreService, 0, 0, object, parameters, null, rsc); + + String expected = "[" + methodName.substring("set".length()) + "]"; + Mockito.verify(statementStorage, Mockito.times(1)).addParameter(object, 0, expected); + Mockito.verifyNoMoreInteractions(statementStorage); + Mockito.verifyZeroInteractions(coreService); + } + + /** + * @return Method names. + */ + @DataProvider(name = "methodsWithBigDataStructures") + public Object[][] methodsWithBigDataStructures() { + return new Object[][] { { "setAsciiStream" }, { "setBinaryStream" }, { "setBlob" }, { "setCharacterStream" }, { "setClob" }, { "setNCharacterStream" }, { "setNClob" }, { "setUnicodeStream" } }; + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/StatementHookTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/StatementHookTest.java new file mode 100644 index 000000000..b8523cef5 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/StatementHookTest.java @@ -0,0 +1,237 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IObjectStorage; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.core.impl.IdManager; +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.util.Timer; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Map; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class StatementHookTest extends AbstractLogSupport { + + @Mock + private Timer timer; + + @Mock + private IdManager idManager; + + @Mock + private ICoreService coreService; + + @Mock + private RegisteredSensorConfig registeredSensorConfig; + + @Mock + private Map parameter; + + @Mock + private ConnectionMetaDataStorage connectionMetaDataStorage; + + @Mock + private StatementReflectionCache statementReflectionCache; + + private StatementHook statementHook; + + private StatementHook statementHook2; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + statementHook = new StatementHook(timer, idManager, connectionMetaDataStorage, statementReflectionCache, parameter); + statementHook2 = new StatementHook(timer, idManager, connectionMetaDataStorage, statementReflectionCache, parameter); + + List list = new ArrayList(); + list.add("java.lang.String"); + when(registeredSensorConfig.getParameterTypes()).thenReturn(list); + } + + @Test + public void oneStatement() throws IdNotAvailableException { + // set up data + long platformId = 1L; + long methodId = 3L; + long registeredMethodId = 13L; + long sensorTypeId = 11L; + long registeredSensorTypeId = 7L; + Object object = mock(Object.class); + Object[] parameters = new Object[1]; + parameters[0] = "SELECT * FROM TEST"; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + + statementHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + verify(timer, times(1)).getCurrentTime(); + + statementHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(timer, times(2)).getCurrentTime(); + + statementHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(idManager).getPlatformId(); + verify(idManager).getRegisteredMethodId(methodId); + verify(idManager).getRegisteredSensorTypeId(sensorTypeId); + + Timestamp timestamp = new Timestamp(GregorianCalendar.getInstance().getTimeInMillis()); + SqlStatementData bsqld = new SqlStatementData(timestamp, platformId, registeredSensorTypeId, registeredMethodId); + bsqld.setSql((String) parameters[0]); + + verify(coreService).addMethodSensorData(eq(sensorTypeId), eq(methodId), (String) Mockito.anyObject(), (MethodSensorData) Mockito.anyObject()); + verify(coreService).getMethodSensorData(eq(sensorTypeId), eq(methodId), (String) Mockito.anyObject()); + verifyNoMoreInteractions(timer, idManager, coreService, registeredSensorConfig); + } + + @Test + public void oneStatementDelegatesStatement() throws IdNotAvailableException { + // set up data + long platformId = 1L; + long methodId = 3L; + long registeredMethodId = 13L; + long sensorTypeId = 11L; + long registeredSensorTypeId = 7L; + Object object = mock(Object.class); + Object[] parameters = new Object[1]; + parameters[0] = "SELECT * FROM TEST"; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + + statementHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + verify(timer, times(1)).getCurrentTime(); + + statementHook2.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + verify(timer, times(2)).getCurrentTime(); + statementHook2.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(timer, times(3)).getCurrentTime(); + statementHook2.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + + statementHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(timer, times(4)).getCurrentTime(); + + statementHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + + verify(idManager, times(2)).getPlatformId(); + verify(idManager, times(2)).getRegisteredMethodId(methodId); + verify(idManager, times(2)).getRegisteredSensorTypeId(sensorTypeId); + + Timestamp timestamp = new Timestamp(GregorianCalendar.getInstance().getTimeInMillis()); + SqlStatementData bsqld = new SqlStatementData(timestamp, platformId, registeredSensorTypeId, registeredMethodId); + bsqld.setSql((String) parameters[0]); + + verify(coreService, times(2)).addMethodSensorData(eq(sensorTypeId), eq(methodId), (String) Mockito.anyObject(), (MethodSensorData) Mockito.anyObject()); + verify(coreService, times(2)).getMethodSensorData(eq(sensorTypeId), eq(methodId), (String) Mockito.anyObject()); + verifyNoMoreInteractions(timer, idManager, coreService, registeredSensorConfig); + } + + @Test + public void platformIdNotAvailable() throws IdNotAvailableException { + // set up data + long methodId = 3L; + long sensorTypeId = 11L; + Object object = mock(Object.class); + Object[] parameters = new Object[1]; + parameters[0] = "SELECT * FROM TEST"; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + doThrow(new IdNotAvailableException("")).when(idManager).getPlatformId(); + + statementHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + statementHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + statementHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + + verify(coreService, never()).addObjectStorage(anyLong(), anyLong(), anyString(), (IObjectStorage) isNull()); + } + + @Test + public void methodIdNotAvailable() throws IdNotAvailableException { + // set up data + long platformId = 1L; + long methodId = 3L; + long sensorTypeId = 11L; + Object object = mock(Object.class); + Object[] parameters = new Object[1]; + parameters[0] = "SELECT * FROM TEST"; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + doThrow(new IdNotAvailableException("")).when(idManager).getRegisteredMethodId(methodId); + + statementHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + statementHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + statementHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + + verify(coreService, never()).addObjectStorage(anyLong(), anyLong(), anyString(), (IObjectStorage) isNull()); + } + + @Test + public void sensorTypeIdNotAvailable() throws IdNotAvailableException { + // set up data + long platformId = 1L; + long methodId = 3L; + long registeredMethodId = 13L; + long sensorTypeId = 11L; + Object object = mock(Object.class); + Object[] parameters = new Object[1]; + parameters[0] = "SELECT * FROM TEST"; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + doThrow(new IdNotAvailableException("")).when(idManager).getRegisteredSensorTypeId(sensorTypeId); + + statementHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + statementHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + statementHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + + verify(coreService, never()).addObjectStorage(anyLong(), anyLong(), anyString(), (IObjectStorage) isNull()); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/StatementSensorTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/StatementSensorTest.java new file mode 100644 index 000000000..09e5e9415 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/StatementSensorTest.java @@ -0,0 +1,45 @@ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import static org.mockito.Mockito.verifyNoMoreInteractions; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.util.Timer; + +import java.util.HashMap; +import java.util.Map; + +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class StatementSensorTest extends AbstractLogSupport { + + @InjectMocks + StatementSensor sqlTimerSensor; + + @Mock + private ConnectionMetaDataStorage connectionMetaDataStorage; + + @Mock + Timer timer; + + @Mock + IIdManager idManager; + + @Mock + StatementReflectionCache statementReflectionCache; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + } + + @Test + public void initSensor() { + Map map = new HashMap(); + sqlTimerSensor.init(map); + verifyNoMoreInteractions(timer, idManager); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/StatementStorageTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/StatementStorageTest.java new file mode 100644 index 000000000..767f9c671 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/method/jdbc/StatementStorageTest.java @@ -0,0 +1,87 @@ +/** + * + */ +package info.novatec.inspectit.agent.sensor.method.jdbc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import info.novatec.inspectit.agent.AbstractLogSupport; + +import java.util.List; + +import org.testng.annotations.Test; + +/** + * @author Stefan Siegl + * + */ +@SuppressWarnings("PMD") +public class StatementStorageTest extends AbstractLogSupport { + + @Test + public void addSQLWithParameterAndRead() { + // Setup + StatementStorage storage = new StatementStorage(); + storage.addSql("SELECT * FROM CARS WHERE CAR_ID = ?"); + Object marker = "I am the prepared Statement"; + storage.addPreparedStatement(marker); + + storage.addParameter(marker, 0, "1"); + + List result = storage.getParameters(marker); + + assertThat(result, contains(equalTo("'1'"))); + } + + @Test + public void addSQLWithoutParameterAndSetParameter() { + // Setup + StatementStorage storage = new StatementStorage(); + storage.addSql("SELECT * FROM CARS"); + Object marker = "I am the prepared Statement"; + storage.addPreparedStatement(marker); + + storage.addParameter(marker, 0, "1"); + + List result = storage.getParameters(marker); + + assertThat(result, is(not(equalTo(null)))); + assertThat(result, is(empty())); + } + + @Test + public void addSQLWithParameterAndAddWrongIndex() { + // Setup + StatementStorage storage = new StatementStorage(); + storage.addSql("SELECT * FROM CARS WHERE CAR_ID = ?"); + Object marker = "I am the prepared Statement"; + storage.addPreparedStatement(marker); + + storage.addParameter(marker, 1, "1"); + + List result = storage.getParameters(marker); + + assertThat(result, is(not(equalTo(null)))); + assertThat(result, contains(equalTo(null))); + } + + @Test + public void addSQLWithParameterAndAddNullValue() { + // Setup + StatementStorage storage = new StatementStorage(); + storage.addSql("SELECT * FROM CARS WHERE CAR_ID = ?"); + Object marker = "I am the prepared Statement"; + storage.addPreparedStatement(marker); + + storage.addParameter(marker, 0, null); + + List result = storage.getParameters(marker); + + assertThat(result, is(not(equalTo(null)))); + assertThat(result, contains(equalTo("null"))); + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/method/timer/TimerHookTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/method/timer/TimerHookTest.java new file mode 100644 index 000000000..f866bed8e --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/method/timer/TimerHookTest.java @@ -0,0 +1,547 @@ +package info.novatec.inspectit.agent.sensor.method.timer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.config.IPropertyAccessor; +import info.novatec.inspectit.agent.config.impl.RegisteredSensorConfig; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IObjectStorage; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.communication.valueobject.TimerRawVO; +import info.novatec.inspectit.communication.valueobject.TimerRawVO.TimerRawContainer; +import info.novatec.inspectit.util.ObjectUtils; +import info.novatec.inspectit.util.Timer; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class TimerHookTest extends AbstractLogSupport { + + @Mock + private Timer timer; + + @Mock + private IIdManager idManager; + + @Mock + private IPropertyAccessor propertyAccessor; + + @Mock + private ICoreService coreService; + + @Mock + private RegisteredSensorConfig registeredSensorConfig; + + @Mock + private ThreadMXBean threadMXBean; + + private TimerHook timerHook; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() { + Map settings = new HashMap(); + settings.put("mode", "raw"); + when(threadMXBean.isThreadCpuTimeEnabled()).thenReturn(true); + when(threadMXBean.isThreadCpuTimeSupported()).thenReturn(true); + timerHook = new TimerHook(timer, idManager, propertyAccessor, settings, threadMXBean); + } + + @Test + public void sameMethodTwice() throws IdNotAvailableException { + // set up data + long platformId = 1L; + long methodId = 3L; + long registeredMethodId = 13L; + long sensorTypeId = 11L; + long registeredSensorTypeId = 7L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.0d; + Double secondTimerValue = 1323.0d; + Double thirdTimerValue = 1894.0d; + Double fourthTimerValue = 2812.0d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue).thenReturn(thirdTimerValue).thenReturn(fourthTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + when(registeredSensorConfig.getSettings()).thenReturn(Collections. singletonMap("charting", Boolean.TRUE)); + + // First call + timerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + verify(timer, times(1)).getCurrentTime(); + + timerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(timer, times(2)).getCurrentTime(); + + timerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(idManager).getPlatformId(); + verify(idManager).getRegisteredMethodId(methodId); + verify(idManager).getRegisteredSensorTypeId(sensorTypeId); + verify(coreService).getObjectStorage(sensorTypeId, methodId, null); + verify(registeredSensorConfig).isPropertyAccess(); + + PlainTimerStorage plainTimerStorage = new PlainTimerStorage(null, platformId, registeredSensorTypeId, registeredMethodId, null, true); + plainTimerStorage.addData(secondTimerValue - firstTimerValue, 0.0d); + verify(coreService).addObjectStorage(eq(sensorTypeId), eq(methodId), (String) eq(null), argThat(new PlainTimerStorageVerifier(plainTimerStorage))); + + // second one + timerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + verify(timer, times(3)).getCurrentTime(); + + timerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(timer, times(4)).getCurrentTime(); + + when(coreService.getObjectStorage(sensorTypeId, methodId, null)).thenReturn(plainTimerStorage); + timerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(coreService, times(2)).getObjectStorage(sensorTypeId, methodId, null); + verify(registeredSensorConfig, times(2)).isPropertyAccess(); + verify(registeredSensorConfig, times(1)).getSettings(); + + TimerRawVO timerRawVO = (TimerRawVO) plainTimerStorage.finalizeDataObject(); + assertThat(timerRawVO.getPlatformIdent(), is(equalTo(platformId))); + assertThat(timerRawVO.getMethodIdent(), is(equalTo(registeredMethodId))); + assertThat(timerRawVO.getSensorTypeIdent(), is(equalTo(registeredSensorTypeId))); + assertThat(((TimerRawContainer) timerRawVO.getData().get(0)).getData()[0], is(equalTo(secondTimerValue - firstTimerValue))); + assertThat(((TimerRawContainer) timerRawVO.getData().get(0)).getData()[1], is(equalTo(fourthTimerValue - thirdTimerValue))); + + verifyNoMoreInteractions(timer, idManager, coreService, registeredSensorConfig); + verifyZeroInteractions(propertyAccessor, object, result); + } + + /** + * Inner class used to verify the contents of PlainTimerData objects. + */ + private static class PlainTimerStorageVerifier extends ArgumentMatcher { + private final ITimerStorage timerStorage; + + public PlainTimerStorageVerifier(PlainTimerStorage timerStorage) { + this.timerStorage = timerStorage; + } + + @Override + public boolean matches(Object object) { + if (!PlainTimerStorage.class.isInstance(object)) { + return false; + } + PlainTimerStorage otherPlainTimerStorage = (PlainTimerStorage) object; + // we receive the raw vo by calling finalize on the object storage! + TimerRawVO timerRawVO = (TimerRawVO) timerStorage.finalizeDataObject(); + TimerRawVO otherTimerRawVO = (TimerRawVO) otherPlainTimerStorage.finalizeDataObject(); + if (timerRawVO.getPlatformIdent() != otherTimerRawVO.getPlatformIdent()) { + return false; + } else if (timerRawVO.getMethodIdent() != otherTimerRawVO.getMethodIdent()) { + return false; + } else if (timerRawVO.getSensorTypeIdent() != otherTimerRawVO.getSensorTypeIdent()) { + return false; + } else if (!ObjectUtils.equals(timerRawVO.getParameterContentData(), otherTimerRawVO.getParameterContentData())) { + return false; + } else if (!timerRawVO.getData().equals(otherTimerRawVO.getData())) { + return false; + } + return true; + } + } + + @Test + public void platformIdNotAvailable() throws IdNotAvailableException { + // set up data + long methodId = 3L; + long sensorTypeId = 11L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + doThrow(new IdNotAvailableException("")).when(idManager).getPlatformId(); + + timerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + timerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + timerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + + verify(coreService, never()).addObjectStorage(anyLong(), anyLong(), anyString(), (IObjectStorage) isNull()); + } + + @Test + public void methodIdNotAvailable() throws IdNotAvailableException { + // set up data + long platformId = 1L; + long methodId = 3L; + long sensorTypeId = 11L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + doThrow(new IdNotAvailableException("")).when(idManager).getRegisteredMethodId(methodId); + + timerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + timerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + timerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + + verify(coreService, never()).addObjectStorage(anyLong(), anyLong(), anyString(), (IObjectStorage) isNull()); + } + + @Test + public void sensorTypeIdNotAvailable() throws IdNotAvailableException { + // set up data + long platformId = 1L; + long methodId = 3L; + long registeredMethodId = 13L; + long sensorTypeId = 11L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + doThrow(new IdNotAvailableException("")).when(idManager).getRegisteredSensorTypeId(sensorTypeId); + + timerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + timerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + timerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + + verify(coreService, never()).addObjectStorage(anyLong(), anyLong(), anyString(), (IObjectStorage) isNull()); + } + + @Test + public void propertyAccess() throws IdNotAvailableException { + // set up data + long platformId = 1L; + long methodId = 3L; + long registeredMethodId = 13L; + long sensorTypeId = 11L; + Object object = mock(Object.class); + Object[] parameters = new Object[2]; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + doThrow(new IdNotAvailableException("")).when(idManager).getRegisteredSensorTypeId(sensorTypeId); + when(registeredSensorConfig.isPropertyAccess()).thenReturn(true); + + timerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + timerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + timerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + + verify(registeredSensorConfig, times(1)).isPropertyAccess(); + verify(propertyAccessor, times(1)).getParameterContentData(registeredSensorConfig.getPropertyAccessorList(), object, parameters, result); + } + + @Test + public void aggregateStorage() throws IdNotAvailableException { + Map settings = new HashMap(); + settings.put("mode", "aggregate"); + timerHook = new TimerHook(timer, idManager, propertyAccessor, settings, ManagementFactory.getThreadMXBean()); + + // set up data + long platformId = 1L; + long methodId = 3L; + long registeredMethodId = 13L; + long sensorTypeId = 11L; + long registeredSensorTypeId = 7L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + + when(registeredSensorConfig.getSettings()).thenReturn(Collections. singletonMap("charting", Boolean.TRUE)); + + timerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + verify(timer, times(1)).getCurrentTime(); + + timerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(timer, times(2)).getCurrentTime(); + + timerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(idManager).getPlatformId(); + verify(idManager).getRegisteredMethodId(methodId); + verify(idManager).getRegisteredSensorTypeId(sensorTypeId); + verify(coreService).getObjectStorage(sensorTypeId, methodId, null); + verify(registeredSensorConfig).isPropertyAccess(); + verify(registeredSensorConfig).getSettings(); + + AggregateTimerStorage aggregateTimerStorage = new AggregateTimerStorage(null, platformId, registeredSensorTypeId, registeredMethodId, null, true); + aggregateTimerStorage.addData(secondTimerValue - firstTimerValue, -1.0d); + verify(coreService).addObjectStorage(eq(sensorTypeId), eq(methodId), (String) eq(null), argThat(new AggregateTimerStorageVerifier(aggregateTimerStorage))); + + verifyNoMoreInteractions(timer, idManager, coreService, registeredSensorConfig); + verifyZeroInteractions(propertyAccessor, object, result); + } + + /** + * Inner class used to verify the contents of AggregateTimerStorage objects. + */ + private static class AggregateTimerStorageVerifier extends ArgumentMatcher { + private final ITimerStorage timerStorage; + + public AggregateTimerStorageVerifier(AggregateTimerStorage timerStorage) { + this.timerStorage = timerStorage; + } + + @Override + public boolean matches(Object object) { + if (!AggregateTimerStorage.class.isInstance(object)) { + return false; + } + + try { + AggregateTimerStorage otherAggregateTimerStorage = (AggregateTimerStorage) object; + // we have to use reflection here + Field field = AggregateTimerStorage.class.getDeclaredField("timerRawVO"); + field.setAccessible(true); + TimerRawVO timerRawVO = (TimerRawVO) field.get(timerStorage); + TimerRawVO otherTimerRawVO = (TimerRawVO) field.get(otherAggregateTimerStorage); + if (timerRawVO.getPlatformIdent() != otherTimerRawVO.getPlatformIdent()) { + return false; + } else if (timerRawVO.getMethodIdent() != otherTimerRawVO.getMethodIdent()) { + return false; + } else if (timerRawVO.getSensorTypeIdent() != otherTimerRawVO.getSensorTypeIdent()) { + return false; + } else if (!ObjectUtils.equals(timerRawVO.getParameterContentData(), otherTimerRawVO.getParameterContentData())) { + return false; + } + return true; + } catch (Exception exception) { + exception.printStackTrace(); + return false; + } + } + } + + @Test + public void optimizedStorage() throws IdNotAvailableException { + Map settings = new HashMap(); + settings.put("mode", "optimized"); + timerHook = new TimerHook(timer, idManager, propertyAccessor, settings, ManagementFactory.getThreadMXBean()); + + // set up data + long platformId = 1L; + long methodId = 3L; + long registeredMethodId = 13L; + long sensorTypeId = 11L; + long registeredSensorTypeId = 7L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + + when(registeredSensorConfig.getSettings()).thenReturn(Collections. singletonMap("charting", Boolean.TRUE)); + + timerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + verify(timer, times(1)).getCurrentTime(); + + timerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(timer, times(2)).getCurrentTime(); + + timerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(idManager).getPlatformId(); + verify(idManager).getRegisteredMethodId(methodId); + verify(idManager).getRegisteredSensorTypeId(sensorTypeId); + verify(coreService).getObjectStorage(sensorTypeId, methodId, null); + verify(registeredSensorConfig).isPropertyAccess(); + verify(registeredSensorConfig).getSettings(); + + OptimizedTimerStorage optimizedTimerStorage = new OptimizedTimerStorage(null, platformId, registeredSensorTypeId, registeredMethodId, null, true); + optimizedTimerStorage.addData(secondTimerValue - firstTimerValue, -1.0d); + verify(coreService).addObjectStorage(eq(sensorTypeId), eq(methodId), (String) eq(null), argThat(new OptimizedTimerStorageVerifier(optimizedTimerStorage))); + + verifyNoMoreInteractions(timer, idManager, coreService, registeredSensorConfig); + verifyZeroInteractions(propertyAccessor, object, result); + } + + /** + * Inner class used to verify the contents of AggregateTimerStorage objects. + */ + private static class OptimizedTimerStorageVerifier extends ArgumentMatcher { + private final ITimerStorage timerStorage; + + public OptimizedTimerStorageVerifier(OptimizedTimerStorage timerStorage) { + this.timerStorage = timerStorage; + } + + @Override + public boolean matches(Object object) { + if (!OptimizedTimerStorage.class.isInstance(object)) { + return false; + } + TimerData timerData = (TimerData) timerStorage.finalizeDataObject(); + TimerData otherTimerData = (TimerData) ((OptimizedTimerStorage) object).finalizeDataObject(); + if (timerData.getPlatformIdent() != otherTimerData.getPlatformIdent()) { + return false; + } else if (timerData.getMethodIdent() != otherTimerData.getMethodIdent()) { + return false; + } else if (timerData.getSensorTypeIdent() != otherTimerData.getSensorTypeIdent()) { + return false; + } else if (timerData.getCount() != otherTimerData.getCount()) { + return false; + } else if (timerData.getDuration() != otherTimerData.getDuration()) { + return false; + } else if (timerData.getMax() != otherTimerData.getMax()) { + return false; + } else if (timerData.getMin() != otherTimerData.getMin()) { + return false; + } else if (timerData.getAverage() != otherTimerData.getAverage()) { + return false; + } else if (!ObjectUtils.equals(timerData.getParameterContentData(), otherTimerData.getParameterContentData())) { + return false; + } else if (timerData.getVariance() != otherTimerData.getVariance()) { + return false; + } + return true; + } + } + + @Test + public void oneRecordWithCpuTime() throws IdNotAvailableException { + // set up data + long platformId = 1L; + long methodId = 3L; + long registeredMethodId = 13L; + long sensorTypeId = 11L; + long registeredSensorTypeId = 7L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + + Long firstCpuTimerValue = 5000L; + Long secondCpuTimerValue = 6872L; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue); + when(threadMXBean.getCurrentThreadCpuTime()).thenReturn(firstCpuTimerValue).thenReturn(secondCpuTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodId)).thenReturn(registeredMethodId); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + + when(registeredSensorConfig.getSettings()).thenReturn(Collections. singletonMap("charting", Boolean.TRUE)); + + timerHook.beforeBody(methodId, sensorTypeId, object, parameters, registeredSensorConfig); + verify(timer, times(1)).getCurrentTime(); + + timerHook.firstAfterBody(methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(timer, times(2)).getCurrentTime(); + + timerHook.secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, result, registeredSensorConfig); + verify(idManager).getPlatformId(); + verify(idManager).getRegisteredMethodId(methodId); + verify(idManager).getRegisteredSensorTypeId(sensorTypeId); + verify(coreService).getObjectStorage(sensorTypeId, methodId, null); + verify(registeredSensorConfig).isPropertyAccess(); + verify(registeredSensorConfig).getSettings(); + + PlainTimerStorage plainTimerStorage = new PlainTimerStorage(null, platformId, registeredSensorTypeId, registeredMethodId, null, true); + plainTimerStorage.addData(secondTimerValue - firstTimerValue, (secondCpuTimerValue - firstCpuTimerValue) / 1000000.0d); + verify(coreService).addObjectStorage(eq(sensorTypeId), eq(methodId), (String) eq(null), argThat(new PlainTimerStorageVerifier(plainTimerStorage))); + + verifyNoMoreInteractions(timer, idManager, coreService, registeredSensorConfig); + verifyZeroInteractions(propertyAccessor, object, result); + } + + @Test + public void twoRecordsWithCpuTime() throws IdNotAvailableException { + long platformId = 1L; + long methodIdOne = 3L; + long registeredMethodIdOne = 13L; + long methodIdTwo = 9L; + long registeredMethodIdTwo = 15L; + long sensorTypeId = 11L; + long registeredSensorTypeId = 7L; + Object object = mock(Object.class); + Object[] parameters = new Object[0]; + Object result = mock(Object.class); + + Double firstTimerValue = 1000.453d; + Double secondTimerValue = 1323.675d; + Double thirdTimerValue = 1578.92d; + Double fourthTimerValue = 2319.712d; + + Long firstCpuTimerValue = 5000L; + Long secondCpuTimerValue = 6872L; + Long thirdCpuTimerValue = 8412L; + Long fourthCpuTimerValue = 15932L; + + when(timer.getCurrentTime()).thenReturn(firstTimerValue).thenReturn(secondTimerValue).thenReturn(thirdTimerValue).thenReturn(fourthTimerValue); + when(threadMXBean.getCurrentThreadCpuTime()).thenReturn(firstCpuTimerValue).thenReturn(secondCpuTimerValue).thenReturn(thirdCpuTimerValue).thenReturn(fourthCpuTimerValue); + when(idManager.getPlatformId()).thenReturn(platformId); + when(idManager.getRegisteredMethodId(methodIdOne)).thenReturn(registeredMethodIdOne); + when(idManager.getRegisteredMethodId(methodIdTwo)).thenReturn(registeredMethodIdTwo); + when(idManager.getRegisteredSensorTypeId(sensorTypeId)).thenReturn(registeredSensorTypeId); + + timerHook.beforeBody(methodIdOne, sensorTypeId, object, parameters, registeredSensorConfig); + timerHook.beforeBody(methodIdTwo, sensorTypeId, object, parameters, registeredSensorConfig); + + timerHook.firstAfterBody(methodIdTwo, sensorTypeId, object, parameters, result, registeredSensorConfig); + timerHook.secondAfterBody(coreService, methodIdTwo, sensorTypeId, object, parameters, result, registeredSensorConfig); + PlainTimerStorage plainTimerStorageTwo = new PlainTimerStorage(null, platformId, registeredSensorTypeId, registeredMethodIdTwo, null, true); + plainTimerStorageTwo.addData(thirdTimerValue - secondTimerValue, (thirdCpuTimerValue - secondCpuTimerValue) / 1000000.0d); + verify(coreService).addObjectStorage(eq(sensorTypeId), eq(methodIdTwo), (String) eq(null), argThat(new PlainTimerStorageVerifier(plainTimerStorageTwo))); + + timerHook.firstAfterBody(methodIdOne, sensorTypeId, object, parameters, result, registeredSensorConfig); + timerHook.secondAfterBody(coreService, methodIdOne, sensorTypeId, object, parameters, result, registeredSensorConfig); + PlainTimerStorage plainTimerStorageOne = new PlainTimerStorage(null, platformId, registeredSensorTypeId, registeredMethodIdOne, null, true); + plainTimerStorageOne.addData(fourthTimerValue - firstTimerValue, (fourthCpuTimerValue - firstCpuTimerValue) / 1000000.0d); + verify(coreService).addObjectStorage(eq(sensorTypeId), eq(methodIdOne), (String) eq(null), argThat(new PlainTimerStorageVerifier(plainTimerStorageOne))); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/platform/ClassLoadingInformationTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/platform/ClassLoadingInformationTest.java new file mode 100644 index 000000000..f870a088f --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/platform/ClassLoadingInformationTest.java @@ -0,0 +1,213 @@ +package info.novatec.inspectit.agent.sensor.platform; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.sensor.platform.provider.RuntimeInfoProvider; +import info.novatec.inspectit.communication.SystemSensorData; +import info.novatec.inspectit.communication.data.ClassLoadingInformationData; + +import java.lang.reflect.Field; +import java.util.logging.Level; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class ClassLoadingInformationTest extends AbstractLogSupport { + + private ClassLoadingInformation classLoadingInfo; + + @Mock + RuntimeInfoProvider runtimeBean; + + @Mock + private IIdManager idManager; + + @Mock + private ICoreService coreService; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { + classLoadingInfo = new ClassLoadingInformation(idManager); + classLoadingInfo.log = LoggerFactory.getLogger(ClassLoadingInformation.class); + + // we have to replace the real runtimeBean by the mocked one, so that we don't retrieve the + // info from the underlying JVM + Field field = classLoadingInfo.getClass().getDeclaredField("runtimeBean"); + field.setAccessible(true); + field.set(classLoadingInfo, runtimeBean); + } + + @Test + public void oneDataSet() throws IdNotAvailableException { + int loadedClassCount = 3; + long totalLoadedClassCount = 10L; + long unloadedClassCount = 2L; + long sensorTypeIdent = 13L; + long platformIdent = 11L; + + when(idManager.getPlatformId()).thenReturn(platformIdent); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenReturn(sensorTypeIdent); + + when(runtimeBean.getLoadedClassCount()).thenReturn(loadedClassCount); + when(runtimeBean.getTotalLoadedClassCount()).thenReturn(totalLoadedClassCount); + when(runtimeBean.getUnloadedClassCount()).thenReturn(unloadedClassCount); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + + classLoadingInfo.update(coreService, sensorTypeIdent); + + // -> The service must create a new one and add it to the storage + // We use an argument capturer to further inspect the given argument. + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + SystemSensorData sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(ClassLoadingInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + ClassLoadingInformationData classLoadingData = (ClassLoadingInformationData) sensorData; + assertThat(classLoadingData.getCount(), is(equalTo(1))); + + // as there was only one data object min/max/total the values must be the + // same + assertThat(classLoadingData.getMinLoadedClassCount(), is(equalTo(loadedClassCount))); + assertThat(classLoadingData.getMaxLoadedClassCount(), is(equalTo(loadedClassCount))); + assertThat(classLoadingData.getTotalLoadedClassCount(), is(equalTo(loadedClassCount))); + + assertThat(classLoadingData.getMinTotalLoadedClassCount(), is(equalTo(totalLoadedClassCount))); + assertThat(classLoadingData.getMaxTotalLoadedClassCount(), is(equalTo(totalLoadedClassCount))); + assertThat(classLoadingData.getTotalTotalLoadedClassCount(), is(equalTo(totalLoadedClassCount))); + + assertThat(classLoadingData.getMinUnloadedClassCount(), is(equalTo(unloadedClassCount))); + assertThat(classLoadingData.getMaxUnloadedClassCount(), is(equalTo(unloadedClassCount))); + assertThat(classLoadingData.getTotalUnloadedClassCount(), is(equalTo(unloadedClassCount))); + } + + @Test + public void twoDataSets() throws IdNotAvailableException { + int loadedClassCount = 3; + int loadedClassCount2 = 5; + long totalLoadedClassCount = 10L; + long totalLoadedClassCount2 = 12L; + long unloadedClassCount = 2L; + long sensorTypeIdent = 13L; + long platformIdent = 11L; + + when(idManager.getPlatformId()).thenReturn(platformIdent); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenReturn(sensorTypeIdent); + + // ------------------------ + // FIRST UPDATE CALL + // ------------------------ + when(runtimeBean.getLoadedClassCount()).thenReturn(loadedClassCount); + when(runtimeBean.getTotalLoadedClassCount()).thenReturn(totalLoadedClassCount); + when(runtimeBean.getUnloadedClassCount()).thenReturn(unloadedClassCount); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + classLoadingInfo.update(coreService, sensorTypeIdent); + + // -> The service must create a new one and add it to the storage + // We use an argument capturer to further inspect the given argument. + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + SystemSensorData parameter = sensorDataCaptor.getValue(); + assertThat(parameter, is(instanceOf(ClassLoadingInformationData.class))); + assertThat(parameter.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(parameter.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + ClassLoadingInformationData classLoadingData = (ClassLoadingInformationData) parameter; + assertThat(classLoadingData.getCount(), is(equalTo(1))); + + // as there was only one data object min/max/total the values must be the + // same + assertThat(classLoadingData.getMinLoadedClassCount(), is(equalTo(loadedClassCount))); + assertThat(classLoadingData.getMaxLoadedClassCount(), is(equalTo(loadedClassCount))); + assertThat(classLoadingData.getTotalLoadedClassCount(), is(equalTo(loadedClassCount))); + + assertThat(classLoadingData.getMinTotalLoadedClassCount(), is(equalTo(totalLoadedClassCount))); + assertThat(classLoadingData.getMaxTotalLoadedClassCount(), is(equalTo(totalLoadedClassCount))); + assertThat(classLoadingData.getTotalTotalLoadedClassCount(), is(equalTo(totalLoadedClassCount))); + + assertThat(classLoadingData.getMinUnloadedClassCount(), is(equalTo(unloadedClassCount))); + assertThat(classLoadingData.getMaxUnloadedClassCount(), is(equalTo(unloadedClassCount))); + assertThat(classLoadingData.getTotalUnloadedClassCount(), is(equalTo(unloadedClassCount))); + + // ------------------------ + // SECOND UPDATE CALL + // ------------------------ + when(runtimeBean.getLoadedClassCount()).thenReturn(loadedClassCount2); + when(runtimeBean.getTotalLoadedClassCount()).thenReturn(totalLoadedClassCount2); + + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(classLoadingData); + classLoadingInfo.update(coreService, sensorTypeIdent); + + // -> The service adds the data object only once + // We use an argument capturer to further inspect the given argument. + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + parameter = sensorDataCaptor.getValue(); + assertThat(parameter, is(instanceOf(ClassLoadingInformationData.class))); + assertThat(parameter.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(parameter.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + classLoadingData = (ClassLoadingInformationData) parameter; + assertThat(classLoadingData.getCount(), is(equalTo(2))); + + assertThat(classLoadingData.getMinLoadedClassCount(), is(equalTo(loadedClassCount))); + assertThat(classLoadingData.getMaxLoadedClassCount(), is(equalTo(loadedClassCount2))); + assertThat(classLoadingData.getTotalLoadedClassCount(), is(equalTo(loadedClassCount + loadedClassCount2))); + + assertThat(classLoadingData.getMinTotalLoadedClassCount(), is(equalTo(totalLoadedClassCount))); + assertThat(classLoadingData.getMaxTotalLoadedClassCount(), is(equalTo(totalLoadedClassCount2))); + assertThat(classLoadingData.getTotalTotalLoadedClassCount(), is(equalTo(totalLoadedClassCount + totalLoadedClassCount2))); + + assertThat(classLoadingData.getMinUnloadedClassCount(), is(equalTo(unloadedClassCount))); + assertThat(classLoadingData.getMaxUnloadedClassCount(), is(equalTo(unloadedClassCount))); + assertThat(classLoadingData.getTotalUnloadedClassCount(), is(equalTo(unloadedClassCount + unloadedClassCount))); + } + + @Test + public void idNotAvailableTest() throws IdNotAvailableException { + int loadedClassCount = 3; + long totalLoadedClassCount = 10L; + long unloadedClassCount = 2L; + long sensorTypeIdent = 13L; + + when(runtimeBean.getLoadedClassCount()).thenReturn(loadedClassCount); + when(runtimeBean.getTotalLoadedClassCount()).thenReturn(totalLoadedClassCount); + when(runtimeBean.getUnloadedClassCount()).thenReturn(unloadedClassCount); + + when(idManager.getPlatformId()).thenThrow(new IdNotAvailableException("expected")); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenThrow(new IdNotAvailableException("expected")); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + + classLoadingInfo.update(coreService, sensorTypeIdent); + + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(0)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + } + + protected Level getLogLevel() { + return Level.FINEST; + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/platform/CompilationInformationTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/platform/CompilationInformationTest.java new file mode 100644 index 000000000..2035acad8 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/platform/CompilationInformationTest.java @@ -0,0 +1,210 @@ +package info.novatec.inspectit.agent.sensor.platform; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.sensor.platform.provider.RuntimeInfoProvider; +import info.novatec.inspectit.communication.SystemSensorData; +import info.novatec.inspectit.communication.data.CompilationInformationData; + +import java.lang.reflect.Field; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class CompilationInformationTest extends AbstractLogSupport { + + private CompilationInformation compilationInfo; + + @Mock + private RuntimeInfoProvider runtimeBean; + + @Mock + private IIdManager idManager; + + @Mock + private ICoreService coreService; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { + compilationInfo = new CompilationInformation(idManager); + compilationInfo.log = LoggerFactory.getLogger(CompilationInformation.class); + + // we have to replace the real runtimeBean by the mocked one, so that we don't retrieve the + // info from the underlying JVM + Field field = compilationInfo.getClass().getDeclaredField("runtimeBean"); + field.setAccessible(true); + field.set(compilationInfo, runtimeBean); + } + + @Test + public void oneDataSet() throws IdNotAvailableException { + long totalCompilationTime = 12345L; + long sensorTypeIdent = 13L; + long platformIdent = 11L; + + when(runtimeBean.getTotalCompilationTime()).thenReturn(totalCompilationTime); + + when(idManager.getPlatformId()).thenReturn(platformIdent); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenReturn(sensorTypeIdent); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + compilationInfo.update(coreService, sensorTypeIdent); + + // -> The service must create a new one and add it to the storage + // We use an argument capturer to further inspect the given argument. + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + SystemSensorData sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(CompilationInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + CompilationInformationData compilationData = (CompilationInformationData) sensorData; + assertThat(compilationData.getCount(), is(equalTo(1))); + + // as there was only one data object min/max/total the values must be the + // same + assertThat(compilationData.getMinTotalCompilationTime(), is(equalTo(totalCompilationTime))); + assertThat(compilationData.getMaxTotalCompilationTime(), is(equalTo(totalCompilationTime))); + assertThat(compilationData.getTotalTotalCompilationTime(), is(equalTo(totalCompilationTime))); + } + + @Test + public void twoDataSets() throws IdNotAvailableException { + long totalCompilationTime = 12345L; + long totalCompilationTime2 = 12359L; + long sensorTypeIdent = 13L; + long platformIdent = 11L; + + when(idManager.getPlatformId()).thenReturn(platformIdent); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenReturn(sensorTypeIdent); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + + // ------------------------ + // FIRST UPDATE CALL + // ------------------------ + when(runtimeBean.getTotalCompilationTime()).thenReturn(totalCompilationTime); + compilationInfo.update(coreService, sensorTypeIdent); + + // -> The service must create a new one and add it to the storage + // We use an argument capturer to further inspect the given argument. + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + SystemSensorData sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(CompilationInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + CompilationInformationData compilationData = (CompilationInformationData) sensorData; + assertThat(compilationData.getCount(), is(equalTo(1))); + + // as there was only one data object min/max/total the values must be the + // same + assertThat(compilationData.getMinTotalCompilationTime(), is(equalTo(totalCompilationTime))); + assertThat(compilationData.getMaxTotalCompilationTime(), is(equalTo(totalCompilationTime))); + assertThat(compilationData.getTotalTotalCompilationTime(), is(equalTo(totalCompilationTime))); + + // ------------------------ + // SECOND UPDATE CALL + // ------------------------ + when(runtimeBean.getTotalCompilationTime()).thenReturn(totalCompilationTime2); + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(compilationData); + compilationInfo.update(coreService, sensorTypeIdent); + + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(CompilationInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + compilationData = (CompilationInformationData) sensorData; + assertThat(compilationData.getCount(), is(equalTo(2))); + + assertThat(compilationData.getMinTotalCompilationTime(), is(equalTo(totalCompilationTime))); + assertThat(compilationData.getMaxTotalCompilationTime(), is(equalTo(totalCompilationTime2))); + assertThat(compilationData.getTotalTotalCompilationTime(), is(equalTo(totalCompilationTime + totalCompilationTime2))); + } + + /** + * Maybe this test is obsolete because we don't expect an exception to be thrown directly in + * {@link CompilationInformation#getTotalCompilationTime()} but only in + * {@link DefaultRuntimeMXBean#getTotalCompilationTime()} + * + * @throws IdNotAvailableException + */ + @Test + public void compilationTimeNotAvailable() throws IdNotAvailableException { + long totalCompilationTime = -1L; + long sensorTypeIdent = 13L; + long platformIdent = 11L; + + when(runtimeBean.getTotalCompilationTime()).thenReturn(-1L); + + when(idManager.getPlatformId()).thenReturn(platformIdent); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenReturn(sensorTypeIdent); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + + compilationInfo.update(coreService, sensorTypeIdent); + + // -> The service must create a new one and add it to the storage + // We use an argument capturer to further inspect the given argument. + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + SystemSensorData sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(CompilationInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + CompilationInformationData compilationData = (CompilationInformationData) sensorData; + assertThat(compilationData.getCount(), is(equalTo(1))); + + // as there was only one data object min/max/total the values must be the + // same + assertThat(compilationData.getMinTotalCompilationTime(), is(equalTo(totalCompilationTime))); + assertThat(compilationData.getMaxTotalCompilationTime(), is(equalTo(totalCompilationTime))); + assertThat(compilationData.getTotalTotalCompilationTime(), is(equalTo(totalCompilationTime))); + } + + @Test + public void idNotAvailableTest() throws IdNotAvailableException { + long totalCompilationTime = 12345L; + long sensorTypeIdent = 13L; + + when(runtimeBean.getTotalCompilationTime()).thenReturn(totalCompilationTime); + + when(idManager.getPlatformId()).thenThrow(new IdNotAvailableException("expected")); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenThrow(new IdNotAvailableException("expected")); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + + compilationInfo.update(coreService, sensorTypeIdent); + + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(0)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/platform/CpuInformationTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/platform/CpuInformationTest.java new file mode 100644 index 000000000..85631cc50 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/platform/CpuInformationTest.java @@ -0,0 +1,208 @@ +package info.novatec.inspectit.agent.sensor.platform; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.closeTo; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.sensor.platform.provider.OperatingSystemInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.RuntimeInfoProvider; +import info.novatec.inspectit.communication.SystemSensorData; +import info.novatec.inspectit.communication.data.CpuInformationData; + +import java.lang.reflect.Field; +import java.util.logging.Level; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class CpuInformationTest extends AbstractLogSupport { + + private CpuInformation cpuInfo; + + @Mock + private OperatingSystemInfoProvider osBean; + + @Mock + private RuntimeInfoProvider runtimeBean; + + @Mock + private IIdManager idManager; + + @Mock + private ICoreService coreService; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { + cpuInfo = new CpuInformation(idManager); + cpuInfo.log = LoggerFactory.getLogger(CpuInformation.class); + + // we have to replace the real osBean by the mocked one, so that we + // don't retrieve the info from the underlying JVM + Field field = cpuInfo.getClass().getDeclaredField("osBean"); + field.setAccessible(true); + field.set(cpuInfo, osBean); + } + + @Test + public void oneDataSet() throws IdNotAvailableException { + int availableProc = 1; + long processCpuTime = 2L; + long sensorType = 13L; + long platformIdent = 11L; + float cpuUsage = 0.0f; + + when(osBean.getAvailableProcessors()).thenReturn(availableProc); + when(osBean.getProcessCpuTime()).thenReturn(processCpuTime); + when(osBean.retrieveCpuUsage()).thenReturn(cpuUsage); + + when(idManager.getPlatformId()).thenReturn(platformIdent); + when(idManager.getRegisteredSensorTypeId(sensorType)).thenReturn(sensorType); + + // no current data object is available + when(coreService.getPlatformSensorData(sensorType)).thenReturn(null); + + cpuInfo.update(coreService, sensorType); + + // -> The service must create a new one and add it to the storage + // We use an argument capturer to further inspect the given argument. + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorType), sensorDataCaptor.capture()); + + // Cast the parameter to the expected concrete class: + SystemSensorData parameter = sensorDataCaptor.getValue(); + assertThat(parameter, is(instanceOf(CpuInformationData.class))); + assertThat(parameter.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(parameter.getSensorTypeIdent(), is(equalTo(sensorType))); + + CpuInformationData data = (CpuInformationData) parameter; + assertThat(data.getCount(), is(1)); + + // CPU usage can only be deduced after two sets of data are captured + assertThat((double) data.getMaxCpuUsage(), is(closeTo(0d, 0.01d))); + assertThat((double) data.getMinCpuUsage(), is(closeTo(0d, 0.01d))); + assertThat((double) data.getTotalCpuUsage(), is(closeTo(0d, 0.01d))); + + assertThat(data.getProcessCpuTime(), is(equalTo(processCpuTime))); + } + + @Test + public void twoDataSets() throws IdNotAvailableException { + int availableProc = 1; + + // process cpu time is provided as nanoseconds + long processCpuTime1 = 200L * 1000 * 1000; // ns representation of 200ms + long processCpuTime2 = 500L * 1000 * 1000; // ns representation of 500ms + + // uptime is provided in milliseconds + long uptime1 = 500L; // 500ms + long uptime2 = 1100L; // 1100ms + long sensorType = 13L; + long platformIdent = 11L; + float cpuUsage1 = 0.0f; + float cpuUsage2 = 50.0f; + + // We use an argument capturer to further inspect the given argument. + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + SystemSensorData parameter = null; + + when(runtimeBean.getUptime()).thenReturn(uptime1).thenReturn(uptime2); + when(osBean.getAvailableProcessors()).thenReturn(availableProc); + when(osBean.getProcessCpuTime()).thenReturn(processCpuTime1).thenReturn(processCpuTime2); + when(osBean.retrieveCpuUsage()).thenReturn(cpuUsage1).thenReturn(cpuUsage2); + + when(idManager.getPlatformId()).thenReturn(platformIdent); + when(idManager.getRegisteredSensorTypeId(sensorType)).thenReturn(sensorType); + + // ------------------------ + // FIRST UPDATE CALL + // ------------------------ + // no current data object is available, second call provides an + // initialized version. The second call provides the parameter that was + // internally registered. + when(coreService.getPlatformSensorData(sensorType)).thenReturn(null); + cpuInfo.update(coreService, sensorType); + + // -> The service must create a new one and add it to the storage + verify(coreService, times(1)).addPlatformSensorData(eq(sensorType), sensorDataCaptor.capture()); + + // Cast the parameter to the expected concrete class: + parameter = sensorDataCaptor.getValue(); + assertThat(parameter, is(instanceOf(CpuInformationData.class))); + assertThat(parameter.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(parameter.getSensorTypeIdent(), is(equalTo(sensorType))); + + CpuInformationData data = (CpuInformationData) parameter; + assertThat(data.getCount(), is(1)); + + // CPU usage can only be deduced after two sets of data are captured + assertThat((double) data.getMaxCpuUsage(), is(closeTo(0d, 0.01d))); + assertThat((double) data.getMinCpuUsage(), is(closeTo(0d, 0.01d))); + assertThat((double) data.getTotalCpuUsage(), is(closeTo(0d, 0.01d))); + + assertThat(data.getProcessCpuTime(), is(equalTo(processCpuTime1))); + + // ------------------------ + // SECOND UPDATE CALL + // ------------------------ + when(coreService.getPlatformSensorData(sensorType)).thenReturn(parameter); + cpuInfo.update(coreService, sensorType); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorType), sensorDataCaptor.capture()); + + // Cast the parameter to the expected concrete class: + parameter = sensorDataCaptor.getValue(); + assertThat(parameter, is(instanceOf(CpuInformationData.class))); + assertThat(parameter.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(parameter.getSensorTypeIdent(), is(equalTo(sensorType))); + + data = (CpuInformationData) parameter; + assertThat(data.getCount(), is(2)); + + // CPU usage can only be deduced after two sets of data are captured + assertThat((double) data.getMaxCpuUsage(), is(closeTo(cpuUsage2, 0.01d))); + + // the first data set was 0 + assertThat((double) data.getMinCpuUsage(), is(closeTo(0d, 0.01d))); + assertThat((double) data.getTotalCpuUsage(), is(closeTo(cpuUsage2, 0.01d))); + + assertThat(data.getProcessCpuTime(), is(equalTo(processCpuTime2))); + } + + @Test + public void idNotAvailableTest() throws IdNotAvailableException { + int availableProc = 1; + long processCpuTime = 2L; + long uptime = 5L; + long sensorType = 13L; + + when(runtimeBean.getUptime()).thenReturn(uptime); + when(osBean.getAvailableProcessors()).thenReturn(availableProc); + when(osBean.getProcessCpuTime()).thenReturn(processCpuTime); + + when(idManager.getPlatformId()).thenThrow(new IdNotAvailableException("expected")); + when(idManager.getRegisteredSensorTypeId(sensorType)).thenThrow(new IdNotAvailableException("expected")); + + // no current data object is available + when(coreService.getPlatformSensorData(sensorType)).thenReturn(null); + cpuInfo.update(coreService, sensorType); + + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(0)).addPlatformSensorData(eq(sensorType), sensorDataCaptor.capture()); + } + + protected Level getLogLevel() { + return Level.FINEST; + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/platform/CpuUsageCalculatorTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/platform/CpuUsageCalculatorTest.java new file mode 100644 index 000000000..c92a125ed --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/platform/CpuUsageCalculatorTest.java @@ -0,0 +1,71 @@ +package info.novatec.inspectit.agent.sensor.platform; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.closeTo; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.sensor.platform.provider.OperatingSystemInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.sun.SunOperatingSystemInfoProvider; + +import java.lang.management.RuntimeMXBean; +import java.lang.reflect.Field; + +import org.mockito.Mock; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.sun.management.OperatingSystemMXBean; + +@SuppressWarnings("PMD") +public class CpuUsageCalculatorTest extends AbstractLogSupport { + + @Mock + private RuntimeMXBean runtimeBean; + + @Mock + private OperatingSystemMXBean osBean; + + private OperatingSystemInfoProvider wrapper; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { + wrapper = new SunOperatingSystemInfoProvider(); + + Field field = wrapper.getClass().getDeclaredField("runtimeBean"); + field.setAccessible(true); + field.set(wrapper, runtimeBean); + + field = wrapper.getClass().getDeclaredField("osBean"); + field.setAccessible(true); + field.set(wrapper, osBean); + } + + @Test + public void calculateCpuUsage() { + int availableProc = 1; + + // process cpu time is provided as nanoseconds + long processCpuTime1 = 200L * 1000 * 1000; // ns representation of 200ms + long processCpuTime2 = 500L * 1000 * 1000; // ns representation of 500ms + + // uptime is provided in milliseconds + long uptime1 = 500L; // 500ms + long uptime2 = 1100L; // 1100ms + + when(runtimeBean.getUptime()).thenReturn(uptime1).thenReturn(uptime2); + when(osBean.getAvailableProcessors()).thenReturn(availableProc); + when(osBean.getProcessCpuTime()).thenReturn(processCpuTime1).thenReturn(processCpuTime2); + + float cpuUsage1 = wrapper.retrieveCpuUsage(); + assertThat((double) cpuUsage1, is(closeTo(0.0d, 0.01d))); + + float cpuUsage2 = wrapper.retrieveCpuUsage(); + // CPU usage can only be deduced after the second call + long process = (processCpuTime2 - processCpuTime1); + long upAsNano = ((uptime2 - uptime1) * 1000 * 1000); + float expectedUsage = (float) process / upAsNano * 100; + assertThat((double) cpuUsage2, is(closeTo((double) expectedUsage, 0.01d))); + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/platform/MemoryInformationTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/platform/MemoryInformationTest.java new file mode 100644 index 000000000..8ca33aa78 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/platform/MemoryInformationTest.java @@ -0,0 +1,320 @@ +package info.novatec.inspectit.agent.sensor.platform; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.sensor.platform.provider.MemoryInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.OperatingSystemInfoProvider; +import info.novatec.inspectit.communication.SystemSensorData; +import info.novatec.inspectit.communication.data.MemoryInformationData; + +import java.lang.management.MemoryUsage; +import java.lang.reflect.Field; +import java.util.logging.Level; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class MemoryInformationTest extends AbstractLogSupport { + + private MemoryInformation memoryInfo; + + @Mock + private MemoryInfoProvider memoryBean; + + @Mock + private OperatingSystemInfoProvider osBean; + + @Mock + private MemoryUsage heapMemoryUsage; + + @Mock + private MemoryUsage nonHeapMemoryUsage; + + @Mock + private IIdManager idManager; + + @Mock + private ICoreService coreService; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { + memoryInfo = new MemoryInformation(idManager); + memoryInfo.log = LoggerFactory.getLogger(MemoryInformation.class); + + // we have to replace the real osBean by the mocked one, so that we don't retrieve the + // info from the underlying JVM + Field field = memoryInfo.getClass().getDeclaredField("osBean"); + field.setAccessible(true); + field.set(memoryInfo, osBean); + + // we have to replace the real memoryBean by the mocked one, so that we don't retrieve the + // info from the underlying JVM + field = memoryInfo.getClass().getDeclaredField("memoryBean"); + field.setAccessible(true); + field.set(memoryInfo, memoryBean); + } + + @Test + public void oneDataSet() throws IdNotAvailableException { + long freePhysicalMemory = 37566L; + long freeSwapSpace = 578300L; + long committedVirtualMemorySize = 12345L; + long usedHeapMemorySize = 3827L; + long usedNonHeapMemorySize = 12200L; + long committedHeapMemorySize = 5056L; + long committedNonHeapMemorySize = 14016L; + long sensorTypeIdent = 13L; + long platformIdent = 11L; + + when(memoryBean.getHeapMemoryUsage()).thenReturn(heapMemoryUsage); + when(memoryBean.getNonHeapMemoryUsage()).thenReturn(nonHeapMemoryUsage); + when(idManager.getPlatformId()).thenReturn(platformIdent); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenReturn(sensorTypeIdent); + + when(osBean.getFreePhysicalMemorySize()).thenReturn(freePhysicalMemory); + when(osBean.getFreeSwapSpaceSize()).thenReturn(freeSwapSpace); + when(osBean.getCommittedVirtualMemorySize()).thenReturn(committedVirtualMemorySize); + when(memoryBean.getHeapMemoryUsage().getCommitted()).thenReturn(committedHeapMemorySize); + when(memoryBean.getHeapMemoryUsage().getUsed()).thenReturn(usedHeapMemorySize); + when(memoryBean.getNonHeapMemoryUsage().getCommitted()).thenReturn(committedNonHeapMemorySize); + when(memoryBean.getNonHeapMemoryUsage().getUsed()).thenReturn(usedNonHeapMemorySize); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + memoryInfo.update(coreService, sensorTypeIdent); + + // -> The service must create a new one and add it to the storage + // We use an argument capturer to further inspect the given argument. + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + SystemSensorData sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(MemoryInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + MemoryInformationData memoryData = (MemoryInformationData) sensorData; + assertThat(memoryData.getCount(), is(equalTo(1))); + + // as there was only one data object min/max/total the values must be the + // same + assertThat(memoryData.getMinComittedHeapMemorySize(), is(equalTo(committedHeapMemorySize))); + assertThat(memoryData.getMaxComittedHeapMemorySize(), is(equalTo(committedHeapMemorySize))); + assertThat(memoryData.getTotalComittedHeapMemorySize(), is(equalTo(committedHeapMemorySize))); + + assertThat(memoryData.getMinComittedNonHeapMemorySize(), is(equalTo(committedNonHeapMemorySize))); + assertThat(memoryData.getMaxComittedNonHeapMemorySize(), is(equalTo(committedNonHeapMemorySize))); + assertThat(memoryData.getTotalComittedNonHeapMemorySize(), is(equalTo(committedNonHeapMemorySize))); + + assertThat(memoryData.getMinComittedVirtualMemSize(), is(equalTo(committedVirtualMemorySize))); + assertThat(memoryData.getMaxComittedVirtualMemSize(), is(equalTo(committedVirtualMemorySize))); + assertThat(memoryData.getTotalComittedVirtualMemSize(), is(equalTo(committedVirtualMemorySize))); + + assertThat(memoryData.getMinFreePhysMemory(), is(equalTo(freePhysicalMemory))); + assertThat(memoryData.getMaxFreePhysMemory(), is(equalTo(freePhysicalMemory))); + assertThat(memoryData.getTotalFreePhysMemory(), is(equalTo(freePhysicalMemory))); + + assertThat(memoryData.getMinFreeSwapSpace(), is(equalTo(freeSwapSpace))); + assertThat(memoryData.getMaxFreeSwapSpace(), is(equalTo(freeSwapSpace))); + assertThat(memoryData.getTotalFreeSwapSpace(), is(equalTo(freeSwapSpace))); + + assertThat(memoryData.getMinUsedHeapMemorySize(), is(equalTo(usedHeapMemorySize))); + assertThat(memoryData.getMaxUsedHeapMemorySize(), is(equalTo(usedHeapMemorySize))); + assertThat(memoryData.getTotalUsedHeapMemorySize(), is(equalTo(usedHeapMemorySize))); + + assertThat(memoryData.getMinUsedNonHeapMemorySize(), is(equalTo(usedNonHeapMemorySize))); + assertThat(memoryData.getMaxUsedNonHeapMemorySize(), is(equalTo(usedNonHeapMemorySize))); + assertThat(memoryData.getTotalUsedNonHeapMemorySize(), is(equalTo(usedNonHeapMemorySize))); + } + + @Test + public void twoDataSets() throws IdNotAvailableException { + long freePhysicalMemory = 37566L; + long freePhysicalMemory2 = 37000L; + long freeSwapSpace = 578300L; + long freeSwapSpace2 = 578000L; + long committedVirtualMemorySize = 12345L; + long committedVirtualMemorySize2 = 12300L; + long usedHeapMemorySize = 3827L; + long usedHeapMemorySize2 = 4000L; + long usedNonHeapMemorySize = 12200L; + long usedNonHeapMemorySize2 = 13000L; + long committedHeapMemorySize = 5056L; + long committedHeapMemorySize2 = 4000L; + long committedNonHeapMemorySize = 14016L; + long committedNonHeapMemorySize2 = 13000L; + long sensorTypeIdent = 13L; + long platformIdent = 11L; + + when(memoryBean.getHeapMemoryUsage()).thenReturn(heapMemoryUsage); + when(memoryBean.getNonHeapMemoryUsage()).thenReturn(nonHeapMemoryUsage); + when(idManager.getPlatformId()).thenReturn(platformIdent); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenReturn(sensorTypeIdent); + + // ------------------------ + // FIRST UPDATE CALL + // ------------------------ + when(osBean.getFreePhysicalMemorySize()).thenReturn(freePhysicalMemory); + when(osBean.getFreeSwapSpaceSize()).thenReturn(freeSwapSpace); + when(osBean.getCommittedVirtualMemorySize()).thenReturn(committedVirtualMemorySize); + when(memoryBean.getHeapMemoryUsage().getCommitted()).thenReturn(committedHeapMemorySize); + when(memoryBean.getHeapMemoryUsage().getUsed()).thenReturn(usedHeapMemorySize); + when(memoryBean.getNonHeapMemoryUsage().getCommitted()).thenReturn(committedNonHeapMemorySize); + when(memoryBean.getNonHeapMemoryUsage().getUsed()).thenReturn(usedNonHeapMemorySize); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + memoryInfo.update(coreService, sensorTypeIdent); + + // -> The service must create a new one and add it to the storage + // We use an argument capturer to further inspect the given argument. + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + SystemSensorData sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(MemoryInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + MemoryInformationData memoryData = (MemoryInformationData) sensorData; + assertThat(memoryData.getCount(), is(equalTo(1))); + + // as there was only one data object min/max/total the values must be the + // same + assertThat(memoryData.getMinComittedHeapMemorySize(), is(equalTo(committedHeapMemorySize))); + assertThat(memoryData.getMaxComittedHeapMemorySize(), is(equalTo(committedHeapMemorySize))); + assertThat(memoryData.getTotalComittedHeapMemorySize(), is(equalTo(committedHeapMemorySize))); + + assertThat(memoryData.getMinComittedNonHeapMemorySize(), is(equalTo(committedNonHeapMemorySize))); + assertThat(memoryData.getMaxComittedNonHeapMemorySize(), is(equalTo(committedNonHeapMemorySize))); + assertThat(memoryData.getTotalComittedNonHeapMemorySize(), is(equalTo(committedNonHeapMemorySize))); + + assertThat(memoryData.getMinComittedVirtualMemSize(), is(equalTo(committedVirtualMemorySize))); + assertThat(memoryData.getMaxComittedVirtualMemSize(), is(equalTo(committedVirtualMemorySize))); + assertThat(memoryData.getTotalComittedVirtualMemSize(), is(equalTo(committedVirtualMemorySize))); + + assertThat(memoryData.getMinFreePhysMemory(), is(equalTo(freePhysicalMemory))); + assertThat(memoryData.getMaxFreePhysMemory(), is(equalTo(freePhysicalMemory))); + assertThat(memoryData.getTotalFreePhysMemory(), is(equalTo(freePhysicalMemory))); + + assertThat(memoryData.getMinFreeSwapSpace(), is(equalTo(freeSwapSpace))); + assertThat(memoryData.getMaxFreeSwapSpace(), is(equalTo(freeSwapSpace))); + assertThat(memoryData.getTotalFreeSwapSpace(), is(equalTo(freeSwapSpace))); + + assertThat(memoryData.getMinUsedHeapMemorySize(), is(equalTo(usedHeapMemorySize))); + assertThat(memoryData.getMaxUsedHeapMemorySize(), is(equalTo(usedHeapMemorySize))); + assertThat(memoryData.getTotalUsedHeapMemorySize(), is(equalTo(usedHeapMemorySize))); + + assertThat(memoryData.getMinUsedNonHeapMemorySize(), is(equalTo(usedNonHeapMemorySize))); + assertThat(memoryData.getMaxUsedNonHeapMemorySize(), is(equalTo(usedNonHeapMemorySize))); + assertThat(memoryData.getTotalUsedNonHeapMemorySize(), is(equalTo(usedNonHeapMemorySize))); + + // ------------------------ + // SECOND UPDATE CALL + // ------------------------ + when(osBean.getFreePhysicalMemorySize()).thenReturn(freePhysicalMemory2); + when(osBean.getFreeSwapSpaceSize()).thenReturn(freeSwapSpace2); + when(osBean.getCommittedVirtualMemorySize()).thenReturn(committedVirtualMemorySize2); + when(memoryBean.getHeapMemoryUsage().getCommitted()).thenReturn(committedHeapMemorySize2); + when(memoryBean.getHeapMemoryUsage().getUsed()).thenReturn(usedHeapMemorySize2); + when(memoryBean.getNonHeapMemoryUsage().getCommitted()).thenReturn(committedNonHeapMemorySize2); + when(memoryBean.getNonHeapMemoryUsage().getUsed()).thenReturn(usedNonHeapMemorySize2); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(memoryData); + memoryInfo.update(coreService, sensorTypeIdent); + + // -> The service must create a new one and add it to the storage + // We use an argument capturer to further inspect the given argument. + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(MemoryInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + memoryData = (MemoryInformationData) sensorData; + assertThat(memoryData.getCount(), is(equalTo(2))); + + // as there was only one data object min/max/total values must be the + // same + assertThat(memoryData.getMinComittedHeapMemorySize(), is(equalTo(committedHeapMemorySize2))); + assertThat(memoryData.getMaxComittedHeapMemorySize(), is(equalTo(committedHeapMemorySize))); + assertThat(memoryData.getTotalComittedHeapMemorySize(), is(equalTo(committedHeapMemorySize + committedHeapMemorySize2))); + + assertThat(memoryData.getMinComittedNonHeapMemorySize(), is(equalTo(committedNonHeapMemorySize2))); + assertThat(memoryData.getMaxComittedNonHeapMemorySize(), is(equalTo(committedNonHeapMemorySize))); + assertThat(memoryData.getTotalComittedNonHeapMemorySize(), is(equalTo(committedNonHeapMemorySize + committedNonHeapMemorySize2))); + + assertThat(memoryData.getMinComittedVirtualMemSize(), is(equalTo(committedVirtualMemorySize2))); + assertThat(memoryData.getMaxComittedVirtualMemSize(), is(equalTo(committedVirtualMemorySize))); + assertThat(memoryData.getTotalComittedVirtualMemSize(), is(equalTo(committedVirtualMemorySize + committedVirtualMemorySize2))); + + assertThat(memoryData.getMinFreePhysMemory(), is(equalTo(freePhysicalMemory2))); + assertThat(memoryData.getMaxFreePhysMemory(), is(equalTo(freePhysicalMemory))); + assertThat(memoryData.getTotalFreePhysMemory(), is(equalTo(freePhysicalMemory + freePhysicalMemory2))); + + assertThat(memoryData.getMinFreeSwapSpace(), is(equalTo(freeSwapSpace2))); + assertThat(memoryData.getMaxFreeSwapSpace(), is(equalTo(freeSwapSpace))); + assertThat(memoryData.getTotalFreeSwapSpace(), is(equalTo(freeSwapSpace + freeSwapSpace2))); + + assertThat(memoryData.getMinUsedHeapMemorySize(), is(equalTo(usedHeapMemorySize))); + assertThat(memoryData.getMaxUsedHeapMemorySize(), is(equalTo(usedHeapMemorySize2))); + assertThat(memoryData.getTotalUsedHeapMemorySize(), is(equalTo(usedHeapMemorySize + usedHeapMemorySize2))); + + assertThat(memoryData.getMinUsedNonHeapMemorySize(), is(equalTo(usedNonHeapMemorySize))); + assertThat(memoryData.getMaxUsedNonHeapMemorySize(), is(equalTo(usedNonHeapMemorySize2))); + assertThat(memoryData.getTotalUsedNonHeapMemorySize(), is(equalTo(usedNonHeapMemorySize + usedNonHeapMemorySize2))); + } + + @Test + public void idNotAvailableTest() throws IdNotAvailableException { + long freePhysicalMemory = 37566L; + long freeSwapSpace = 578300L; + long committedVirtualMemorySize = 12345L; + long usedHeapMemorySize = 3827L; + long usedNonHeapMemorySize = 12200L; + long committedHeapMemorySize = 5056L; + long committedNonHeapMemorySize = 14016L; + long sensorTypeIdent = 13L; + + when(memoryBean.getHeapMemoryUsage()).thenReturn(heapMemoryUsage); + when(memoryBean.getNonHeapMemoryUsage()).thenReturn(nonHeapMemoryUsage); + + when(osBean.getFreePhysicalMemorySize()).thenReturn(freePhysicalMemory); + when(osBean.getFreeSwapSpaceSize()).thenReturn(freeSwapSpace); + when(osBean.getCommittedVirtualMemorySize()).thenReturn(committedVirtualMemorySize); + when(memoryBean.getHeapMemoryUsage().getCommitted()).thenReturn(committedHeapMemorySize); + when(memoryBean.getHeapMemoryUsage().getUsed()).thenReturn(usedHeapMemorySize); + when(memoryBean.getNonHeapMemoryUsage().getCommitted()).thenReturn(committedNonHeapMemorySize); + when(memoryBean.getNonHeapMemoryUsage().getUsed()).thenReturn(usedNonHeapMemorySize); + + when(idManager.getPlatformId()).thenThrow(new IdNotAvailableException("expected")); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenThrow(new IdNotAvailableException("expected")); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + memoryInfo.update(coreService, sensorTypeIdent); + + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(0)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + } + + protected Level getLogLevel() { + return Level.FINEST; + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/platform/RuntimeInformationTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/platform/RuntimeInformationTest.java new file mode 100644 index 000000000..b152f9198 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/platform/RuntimeInformationTest.java @@ -0,0 +1,171 @@ +package info.novatec.inspectit.agent.sensor.platform; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.sensor.platform.provider.RuntimeInfoProvider; +import info.novatec.inspectit.communication.SystemSensorData; +import info.novatec.inspectit.communication.data.RuntimeInformationData; + +import java.lang.reflect.Field; +import java.util.logging.Level; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class RuntimeInformationTest extends AbstractLogSupport { + + private RuntimeInformation runtimeInfo; + + @Mock + private RuntimeInfoProvider runtimeBean; + + @Mock + private IIdManager idManager; + + @Mock + private ICoreService coreService; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { + runtimeInfo = new RuntimeInformation(idManager); + runtimeInfo.log = LoggerFactory.getLogger(RuntimeInformation.class); + + // we have to replace the real runtimeBean by the mocked one, so that we don't retrieve the + // info from the underlying JVM + Field field = runtimeInfo.getClass().getDeclaredField("runtimeBean"); + field.setAccessible(true); + field.set(runtimeInfo, runtimeBean); + } + + @Test + public void oneDataSet() throws IdNotAvailableException { + long uptime = 12345L; + long sensorTypeIdent = 13L; + long platformIdent = 11L; + + when(runtimeBean.getUptime()).thenReturn(uptime); + + when(idManager.getPlatformId()).thenReturn(platformIdent); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenReturn(sensorTypeIdent); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + runtimeInfo.update(coreService, sensorTypeIdent); + + // -> The service must create a new one and add it to the storage + // We use an argument capturer to further inspect the given argument. + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + SystemSensorData sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(RuntimeInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + RuntimeInformationData runtimeData = (RuntimeInformationData) sensorData; + assertThat(runtimeData.getCount(), is(equalTo(1))); + + // as there was only one data object min/max/total the values must be the + // same + assertThat(runtimeData.getMinUptime(), is(equalTo(uptime))); + assertThat(runtimeData.getMaxUptime(), is(equalTo(uptime))); + assertThat(runtimeData.getTotalUptime(), is(equalTo(uptime))); + } + + @Test + public void twoDataSets() throws IdNotAvailableException { + long uptime = 12345L; + long uptime2 = 123559L; + long sensorTypeIdent = 13L; + long platformIdent = 11L; + + when(idManager.getPlatformId()).thenReturn(platformIdent); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenReturn(sensorTypeIdent); + + // ------------------------ + // FIRST UPDATE CALL + // ------------------------ + when(runtimeBean.getUptime()).thenReturn(uptime); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + runtimeInfo.update(coreService, sensorTypeIdent); + + // -> The service must create a new one and add it to the storage + // We use an argument capturer to further inspect the given argument. + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + SystemSensorData sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(RuntimeInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + RuntimeInformationData runtimeData = (RuntimeInformationData) sensorData; + assertThat(runtimeData.getCount(), is(equalTo(1))); + + // as there was only one data object min/max/total the values must be the + // same + assertThat(runtimeData.getMinUptime(), is(equalTo(uptime))); + assertThat(runtimeData.getMaxUptime(), is(equalTo(uptime))); + assertThat(runtimeData.getTotalUptime(), is(equalTo(uptime))); + + // ------------------------ + // SECOND UPDATE CALL + // ------------------------ + when(runtimeBean.getUptime()).thenReturn(uptime2); + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(runtimeData); + + runtimeInfo.update(coreService, sensorTypeIdent); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(RuntimeInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + runtimeData = (RuntimeInformationData) sensorData; + assertThat(runtimeData.getCount(), is(equalTo(2))); + + assertThat(runtimeData.getMinUptime(), is(equalTo(uptime))); + assertThat(runtimeData.getMaxUptime(), is(equalTo(uptime2))); + assertThat(runtimeData.getTotalUptime(), is(equalTo(uptime + uptime2))); + } + + @Test + public void idNotAvailableTest() throws IdNotAvailableException { + long uptime = 12345L; + long sensorTypeIdent = 13L; + + when(runtimeBean.getUptime()).thenReturn(uptime); + + when(idManager.getPlatformId()).thenThrow(new IdNotAvailableException("expected")); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenThrow(new IdNotAvailableException("expected")); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + runtimeInfo.update(coreService, sensorTypeIdent); + + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(0)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + } + + protected Level getLogLevel() { + return Level.FINEST; + } + +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/platform/SystemInformationTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/platform/SystemInformationTest.java new file mode 100644 index 000000000..ada70c878 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/platform/SystemInformationTest.java @@ -0,0 +1,449 @@ +package info.novatec.inspectit.agent.sensor.platform; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.sensor.platform.provider.MemoryInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.OperatingSystemInfoProvider; +import info.novatec.inspectit.agent.sensor.platform.provider.RuntimeInfoProvider; +import info.novatec.inspectit.communication.SystemSensorData; +import info.novatec.inspectit.communication.data.SystemInformationData; + +import java.lang.management.MemoryUsage; +import java.lang.reflect.Field; +import java.util.logging.Level; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class SystemInformationTest extends AbstractLogSupport { + + private SystemInformation systemInfo; + + @Mock + private MemoryInfoProvider memoryBean; + + @Mock + private OperatingSystemInfoProvider osBean; + + @Mock + private RuntimeInfoProvider runtimeBean; + + @Mock + private MemoryUsage heapMemoryUsage; + + @Mock + private MemoryUsage nonHeapMemoryUsage; + + @Mock + private IIdManager idManager; + + @Mock + private ICoreService coreService; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { + systemInfo = new SystemInformation(idManager); + systemInfo.log = LoggerFactory.getLogger(SystemInformation.class); + + // we have to replace the real osBean by the mocked one, so that we don't retrieve the + // info from the underlying JVM + Field field = systemInfo.getClass().getDeclaredField("osBean"); + field.setAccessible(true); + field.set(systemInfo, osBean); + + // we have to replace the real memoryBean by the mocked one, so that we don't retrieve the + // info from the underlying JVM + field = systemInfo.getClass().getDeclaredField("memoryBean"); + field.setAccessible(true); + field.set(systemInfo, memoryBean); + + // we have to replace the real runtimeBean by the mocked one, so that we don't retrieve the + // info from the underlying JVM + field = systemInfo.getClass().getDeclaredField("runtimeBean"); + field.setAccessible(true); + field.set(systemInfo, runtimeBean); + } + + @Test + public void oneStaticDataSet() throws IdNotAvailableException { + long totalPhysMemory = 775000L; + long totalSwapSpace = 555000L; + int availableProcessors = 4; + String architecture = "i386"; + String osName = "linux"; + String osVersion = "2.26"; + String jitCompilerName = "HotSpot Client Compiler"; + String classPath = "thisIsTheClassPath"; + String bootClassPath = "thisIsTheBootClassPath"; + String libraryPath = "thisIsTheLibraryPath"; + String vmVendor = "Sun Microsystems"; + String vmVersion = "1.5.0_15"; + String vmName = "inspectit-vm"; + String vmSpecName = "Java Virtual Machine"; + long initHeapMemorySize = 4000L; + long maxHeapMemorySize = 10000L; + long initNonHeapMemorySize = 12000L; + long maxNonHeapMemorySize = 14000L; + long sensorTypeIdent = 13L; + long platformIdent = 11L; + + when(idManager.getPlatformId()).thenReturn(platformIdent); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenReturn(sensorTypeIdent); + when(memoryBean.getHeapMemoryUsage()).thenReturn(heapMemoryUsage); + when(memoryBean.getNonHeapMemoryUsage()).thenReturn(nonHeapMemoryUsage); + + when(osBean.getArch()).thenReturn(architecture); + when(osBean.getAvailableProcessors()).thenReturn(availableProcessors); + when(osBean.getTotalPhysicalMemorySize()).thenReturn(totalPhysMemory); + when(osBean.getTotalSwapSpaceSize()).thenReturn(totalSwapSpace); + when(osBean.getVersion()).thenReturn(osVersion); + when(osBean.getName()).thenReturn(osName); + when(runtimeBean.getJitCompilerName()).thenReturn(jitCompilerName); + when(runtimeBean.getClassPath()).thenReturn(classPath); + when(runtimeBean.getBootClassPath()).thenReturn(bootClassPath); + when(runtimeBean.getLibraryPath()).thenReturn(libraryPath); + when(runtimeBean.getVmName()).thenReturn(vmName); + when(runtimeBean.getVmVendor()).thenReturn(vmVendor); + when(runtimeBean.getVmVersion()).thenReturn(vmVersion); + when(runtimeBean.getSpecName()).thenReturn(vmSpecName); + when(memoryBean.getHeapMemoryUsage().getInit()).thenReturn(initHeapMemorySize); + when(memoryBean.getHeapMemoryUsage().getMax()).thenReturn(maxHeapMemorySize); + when(memoryBean.getNonHeapMemoryUsage().getInit()).thenReturn(initNonHeapMemorySize); + when(memoryBean.getNonHeapMemoryUsage().getMax()).thenReturn(maxNonHeapMemorySize); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + + systemInfo.update(coreService, sensorTypeIdent); + + // -> The service must create a new one and add it to the storage + // We use an argument capturer to further inspect the given argument. + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + SystemSensorData sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(SystemInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + SystemInformationData systemData = (SystemInformationData) sensorData; + + // as there was only one data object values must be the + // same + assertThat(systemData.getArchitecture(), is(equalTo(architecture))); + assertThat(systemData.getAvailableProcessors(), is(equalTo(availableProcessors))); + assertThat(systemData.getBootClassPath(), is(equalTo(bootClassPath))); + assertThat(systemData.getClassPath(), is(equalTo(classPath))); + assertThat(systemData.getInitHeapMemorySize(), is(equalTo(initHeapMemorySize))); + assertThat(systemData.getInitNonHeapMemorySize(), is(equalTo(initNonHeapMemorySize))); + assertThat(systemData.getJitCompilerName(), is(equalTo(jitCompilerName))); + assertThat(systemData.getLibraryPath(), is(equalTo(libraryPath))); + assertThat(systemData.getMaxHeapMemorySize(), is(equalTo(maxHeapMemorySize))); + assertThat(systemData.getMaxNonHeapMemorySize(), is(equalTo(maxNonHeapMemorySize))); + assertThat(systemData.getOsName(), is(equalTo(osName))); + assertThat(systemData.getOsVersion(), is(equalTo(osVersion))); + assertThat(systemData.getTotalPhysMemory(), is(equalTo(totalPhysMemory))); + assertThat(systemData.getTotalSwapSpace(), is(equalTo(totalSwapSpace))); + assertThat(systemData.getVmName(), is(equalTo(vmName))); + assertThat(systemData.getVmSpecName(), is(equalTo(vmSpecName))); + assertThat(systemData.getVmVendor(), is(equalTo(vmVendor))); + assertThat(systemData.getVmVersion(), is(equalTo(vmVersion))); + } + + /** + * This testcase combines different testcases that simulate the absense of static information. + * Realizing each case separately would require many code with almost no additional value. + * + * Maybe this test is obsolete because we don't expect an exception to be thrown directly in + * {@link SystemInformation} but only in the getter methods of {@link DefaultRuntimeMXBean} + * + * @throws IdNotAvailableException + */ + @Test + public void informationNotAvailable() throws IdNotAvailableException { + long totalPhysMemory = 775000L; + long totalSwapSpace = 555000L; + int availableProcessors = 4; + String empty = ""; + String jitCompilerName = "HotSpot Client Compiler"; + String vmName = "inspectit-vm"; + long initHeapMemorySize = 4000L; + long maxHeapMemorySize = 10000L; + long initNonHeapMemorySize = 12000L; + long maxNonHeapMemorySize = 14000L; + long sensorTypeIdent = 13L; + long platformIdent = 11L; + + when(idManager.getPlatformId()).thenReturn(platformIdent); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenReturn(sensorTypeIdent); + when(memoryBean.getHeapMemoryUsage()).thenReturn(heapMemoryUsage); + when(memoryBean.getNonHeapMemoryUsage()).thenReturn(nonHeapMemoryUsage); + + when(osBean.getArch()).thenReturn(""); + when(osBean.getAvailableProcessors()).thenReturn(availableProcessors); + when(osBean.getTotalPhysicalMemorySize()).thenReturn(totalPhysMemory); + when(osBean.getTotalSwapSpaceSize()).thenReturn(totalSwapSpace); + when(osBean.getVersion()).thenReturn(""); + when(osBean.getName()).thenReturn(""); + when(runtimeBean.getJitCompilerName()).thenReturn(jitCompilerName); + when(runtimeBean.getClassPath()).thenReturn(""); + when(runtimeBean.getBootClassPath()).thenReturn(""); + when(runtimeBean.getLibraryPath()).thenReturn(""); + when(runtimeBean.getVmName()).thenReturn(vmName); + when(runtimeBean.getVmVendor()).thenReturn(""); + when(runtimeBean.getVmVersion()).thenReturn(""); + when(runtimeBean.getSpecName()).thenReturn(""); + when(memoryBean.getHeapMemoryUsage().getInit()).thenReturn(initHeapMemorySize); + when(memoryBean.getHeapMemoryUsage().getMax()).thenReturn(maxHeapMemorySize); + when(memoryBean.getNonHeapMemoryUsage().getInit()).thenReturn(initNonHeapMemorySize); + when(memoryBean.getNonHeapMemoryUsage().getMax()).thenReturn(maxNonHeapMemorySize); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + systemInfo.update(coreService, sensorTypeIdent); + + // -> The service must create a new one and add it to the storage + // We use an argument capturer to further inspect the given argument. + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + SystemSensorData sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(SystemInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + SystemInformationData systemData = (SystemInformationData) sensorData; + + // as there was only one data object the values must be the + // same + assertThat(systemData.getArchitecture(), is(equalTo(empty))); + assertThat(systemData.getAvailableProcessors(), is(equalTo(availableProcessors))); + assertThat(systemData.getBootClassPath(), is(equalTo(empty))); + assertThat(systemData.getClassPath(), is(equalTo(empty))); + assertThat(systemData.getInitHeapMemorySize(), is(equalTo(initHeapMemorySize))); + assertThat(systemData.getInitNonHeapMemorySize(), is(equalTo(initNonHeapMemorySize))); + assertThat(systemData.getJitCompilerName(), is(equalTo(jitCompilerName))); + assertThat(systemData.getLibraryPath(), is(equalTo(empty))); + assertThat(systemData.getMaxHeapMemorySize(), is(equalTo(maxHeapMemorySize))); + assertThat(systemData.getMaxNonHeapMemorySize(), is(equalTo(maxNonHeapMemorySize))); + assertThat(systemData.getOsName(), is(equalTo(empty))); + assertThat(systemData.getOsVersion(), is(equalTo(empty))); + assertThat(systemData.getTotalPhysMemory(), is(equalTo(totalPhysMemory))); + assertThat(systemData.getTotalSwapSpace(), is(equalTo(totalSwapSpace))); + assertThat(systemData.getVmName(), is(equalTo(vmName))); + assertThat(systemData.getVmSpecName(), is(equalTo(empty))); + assertThat(systemData.getVmVendor(), is(equalTo(empty))); + assertThat(systemData.getVmVersion(), is(equalTo(empty))); + } + + /** + * Maybe this test is obsolete because we don't expect an exception to be thrown directly in + * {@link SystemInformation#getBootClassPath()} but only in + * {@link DefaultRuntimeMXBean#getBootClassPath()} + * + * @throws IdNotAvailableException + */ + @Test + public void bootClassPathNotSupported() throws IdNotAvailableException { + long totalPhysMemory = 775000L; + long totalSwapSpace = 555000L; + int availableProcessors = 4; + String architecture = "i386"; + String osName = "linux"; + String osVersion = "2.26"; + String jitCompilerName = "HotSpot Client Compiler"; + String classPath = "thisIsTheClassPath"; + String bootClassPath = ""; + String libraryPath = "thisIsTheLibraryPath"; + String vmVendor = "Sun Microsystems"; + String vmVersion = "1.5.0_15"; + String vmName = "inspectit-vm"; + String vmSpecName = "Java Virtual Machine"; + long initHeapMemorySize = 4000L; + long maxHeapMemorySize = 10000L; + long initNonHeapMemorySize = 12000L; + long maxNonHeapMemorySize = 14000L; + long sensorTypeIdent = 13L; + long platformIdent = 11L; + + when(idManager.getPlatformId()).thenReturn(platformIdent); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenReturn(sensorTypeIdent); + when(memoryBean.getHeapMemoryUsage()).thenReturn(heapMemoryUsage); + when(memoryBean.getNonHeapMemoryUsage()).thenReturn(nonHeapMemoryUsage); + + when(osBean.getArch()).thenReturn(architecture); + when(osBean.getAvailableProcessors()).thenReturn(availableProcessors); + when(osBean.getTotalPhysicalMemorySize()).thenReturn(totalPhysMemory); + when(osBean.getTotalSwapSpaceSize()).thenReturn(totalSwapSpace); + when(osBean.getVersion()).thenReturn(osVersion); + when(osBean.getName()).thenReturn(osName); + when(runtimeBean.getJitCompilerName()).thenReturn(jitCompilerName); + when(runtimeBean.getClassPath()).thenReturn(classPath); + when(runtimeBean.getBootClassPath()).thenReturn(""); + when(runtimeBean.getLibraryPath()).thenReturn(libraryPath); + when(runtimeBean.getVmName()).thenReturn(vmName); + when(runtimeBean.getVmVendor()).thenReturn(vmVendor); + when(runtimeBean.getVmVersion()).thenReturn(vmVersion); + when(runtimeBean.getSpecName()).thenReturn(vmSpecName); + when(memoryBean.getHeapMemoryUsage().getInit()).thenReturn(initHeapMemorySize); + when(memoryBean.getHeapMemoryUsage().getMax()).thenReturn(maxHeapMemorySize); + when(memoryBean.getNonHeapMemoryUsage().getInit()).thenReturn(initNonHeapMemorySize); + when(memoryBean.getNonHeapMemoryUsage().getMax()).thenReturn(maxNonHeapMemorySize); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + systemInfo.update(coreService, sensorTypeIdent); + + // -> The service must create a new one and add it to the storage + // We use an argument capturer to further inspect the given argument. + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + SystemSensorData sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(SystemInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + SystemInformationData systemData = (SystemInformationData) sensorData; + + // as there was only one data object values must be the + // same + assertThat(systemData.getArchitecture(), is(equalTo(architecture))); + assertThat(systemData.getAvailableProcessors(), is(equalTo(availableProcessors))); + assertThat(systemData.getBootClassPath(), is(equalTo(bootClassPath))); + assertThat(systemData.getClassPath(), is(equalTo(classPath))); + assertThat(systemData.getInitHeapMemorySize(), is(equalTo(initHeapMemorySize))); + assertThat(systemData.getInitNonHeapMemorySize(), is(equalTo(initNonHeapMemorySize))); + assertThat(systemData.getJitCompilerName(), is(equalTo(jitCompilerName))); + assertThat(systemData.getLibraryPath(), is(equalTo(libraryPath))); + assertThat(systemData.getMaxHeapMemorySize(), is(equalTo(maxHeapMemorySize))); + assertThat(systemData.getMaxNonHeapMemorySize(), is(equalTo(maxNonHeapMemorySize))); + assertThat(systemData.getOsName(), is(equalTo(osName))); + assertThat(systemData.getOsVersion(), is(equalTo(osVersion))); + assertThat(systemData.getTotalPhysMemory(), is(equalTo(totalPhysMemory))); + assertThat(systemData.getTotalSwapSpace(), is(equalTo(totalSwapSpace))); + assertThat(systemData.getVmName(), is(equalTo(vmName))); + assertThat(systemData.getVmSpecName(), is(equalTo(vmSpecName))); + assertThat(systemData.getVmVendor(), is(equalTo(vmVendor))); + assertThat(systemData.getVmVersion(), is(equalTo(vmVersion))); + } + + @Test + public void valueTooLong() throws IdNotAvailableException { + String tooLong = fillString('x', 10001); + String limit = fillString('x', 10000); + + long totalPhysMemory = 775000L; + long totalSwapSpace = 555000L; + int availableProcessors = 4; + String architecture = "i386"; + String osName = "linux"; + String osVersion = "2.26"; + String jitCompilerName = "HotSpot Client Compiler"; + String vmVendor = "Sun Microsystems"; + String vmVersion = "1.5.0_15"; + String vmName = "inspectit-vm"; + String vmSpecName = "Java Virtual Machine"; + long initHeapMemorySize = 4000L; + long maxHeapMemorySize = 10000L; + long initNonHeapMemorySize = 12000L; + long maxNonHeapMemorySize = 14000L; + long sensorTypeIdent = 13L; + long platformIdent = 11L; + + when(idManager.getPlatformId()).thenReturn(platformIdent); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenReturn(sensorTypeIdent); + when(memoryBean.getHeapMemoryUsage()).thenReturn(heapMemoryUsage); + when(memoryBean.getNonHeapMemoryUsage()).thenReturn(nonHeapMemoryUsage); + + when(osBean.getArch()).thenReturn(architecture); + when(osBean.getAvailableProcessors()).thenReturn(availableProcessors); + when(osBean.getTotalPhysicalMemorySize()).thenReturn(totalPhysMemory); + when(osBean.getTotalSwapSpaceSize()).thenReturn(totalSwapSpace); + when(osBean.getVersion()).thenReturn(osVersion); + when(osBean.getName()).thenReturn(osName); + when(runtimeBean.getJitCompilerName()).thenReturn(jitCompilerName); + when(runtimeBean.getClassPath()).thenReturn(tooLong); + when(runtimeBean.getBootClassPath()).thenReturn(tooLong); + when(runtimeBean.getLibraryPath()).thenReturn(tooLong); + when(runtimeBean.getVmName()).thenReturn(vmName); + when(runtimeBean.getVmVendor()).thenReturn(vmVendor); + when(runtimeBean.getVmVersion()).thenReturn(vmVersion); + when(runtimeBean.getSpecName()).thenReturn(vmSpecName); + when(memoryBean.getHeapMemoryUsage().getInit()).thenReturn(initHeapMemorySize); + when(memoryBean.getHeapMemoryUsage().getMax()).thenReturn(maxHeapMemorySize); + when(memoryBean.getNonHeapMemoryUsage().getInit()).thenReturn(initNonHeapMemorySize); + when(memoryBean.getNonHeapMemoryUsage().getMax()).thenReturn(maxNonHeapMemorySize); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + systemInfo.update(coreService, sensorTypeIdent); + + // -> The service must create a new one and add it to the storage + // We use an argument capturer to further inspect the given argument. + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + SystemSensorData sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(SystemInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + SystemInformationData systemData = (SystemInformationData) sensorData; + + // as there was only one data object the values must be the + // same + assertThat(systemData.getArchitecture(), is(equalTo(architecture))); + assertThat(systemData.getAvailableProcessors(), is(equalTo(availableProcessors))); + assertThat(systemData.getBootClassPath(), is(equalTo(limit))); + assertThat(systemData.getClassPath(), is(equalTo(limit))); + assertThat(systemData.getInitHeapMemorySize(), is(equalTo(initHeapMemorySize))); + assertThat(systemData.getInitNonHeapMemorySize(), is(equalTo(initNonHeapMemorySize))); + assertThat(systemData.getJitCompilerName(), is(equalTo(jitCompilerName))); + assertThat(systemData.getLibraryPath(), is(equalTo(limit))); + assertThat(systemData.getMaxHeapMemorySize(), is(equalTo(maxHeapMemorySize))); + assertThat(systemData.getMaxNonHeapMemorySize(), is(equalTo(maxNonHeapMemorySize))); + assertThat(systemData.getOsName(), is(equalTo(osName))); + assertThat(systemData.getOsVersion(), is(equalTo(osVersion))); + assertThat(systemData.getTotalPhysMemory(), is(equalTo(totalPhysMemory))); + assertThat(systemData.getTotalSwapSpace(), is(equalTo(totalSwapSpace))); + assertThat(systemData.getVmName(), is(equalTo(vmName))); + assertThat(systemData.getVmSpecName(), is(equalTo(vmSpecName))); + assertThat(systemData.getVmVendor(), is(equalTo(vmVendor))); + assertThat(systemData.getVmVersion(), is(equalTo(vmVersion))); + } + + /** + * Creates a new String with the specified length. + * + * @param character + * @param count + * @return + */ + private String fillString(char character, int count) { + // creates a string of 'x' repeating characters + char[] chars = new char[count]; + while (count > 0) { + chars[--count] = character; + } + return new String(chars); + } + + protected Level getLogLevel() { + return Level.FINEST; + } +} diff --git a/Agent/test/info/novatec/inspectit/agent/sensor/platform/ThreadInformationTest.java b/Agent/test/info/novatec/inspectit/agent/sensor/platform/ThreadInformationTest.java new file mode 100644 index 000000000..2ba2410eb --- /dev/null +++ b/Agent/test/info/novatec/inspectit/agent/sensor/platform/ThreadInformationTest.java @@ -0,0 +1,231 @@ +package info.novatec.inspectit.agent.sensor.platform; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.agent.AbstractLogSupport; +import info.novatec.inspectit.agent.core.ICoreService; +import info.novatec.inspectit.agent.core.IIdManager; +import info.novatec.inspectit.agent.core.IdNotAvailableException; +import info.novatec.inspectit.agent.sensor.platform.provider.ThreadInfoProvider; +import info.novatec.inspectit.communication.SystemSensorData; +import info.novatec.inspectit.communication.data.ThreadInformationData; + +import java.lang.reflect.Field; +import java.util.logging.Level; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class ThreadInformationTest extends AbstractLogSupport { + + private ThreadInformation threadInfo; + + @Mock + private ThreadInfoProvider threadBean; + + @Mock + private IIdManager idManager; + + @Mock + private ICoreService coreService; + + @BeforeMethod(dependsOnMethods = { "initMocks" }) + public void initTestClass() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { + threadInfo = new ThreadInformation(idManager); + threadInfo.log = LoggerFactory.getLogger(ThreadInformation.class); + + // we have to replace the real threadBean by the mocked one, so that we don't retrieve the + // info from the underlying JVM + Field field = threadInfo.getClass().getDeclaredField("threadBean"); + field.setAccessible(true); + field.set(threadInfo, threadBean); + } + + @Test + public void oneDataSet() throws IdNotAvailableException { + int daemonThreadCount = 5; + int threadCount = 13; + int peakThreadCount = 25; + long totalStartedThreadCount = 55L; + long sensorTypeIdent = 13L; + long platformIdent = 11L; + + when(threadBean.getDaemonThreadCount()).thenReturn(daemonThreadCount); + when(threadBean.getThreadCount()).thenReturn(threadCount); + when(threadBean.getPeakThreadCount()).thenReturn(peakThreadCount); + when(threadBean.getTotalStartedThreadCount()).thenReturn(totalStartedThreadCount); + + when(idManager.getPlatformId()).thenReturn(platformIdent); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenReturn(sensorTypeIdent); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + threadInfo.update(coreService, sensorTypeIdent); + + // -> The service must create a new one and add it to the storage + // We use an argument capturer to further inspect the given argument. + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + SystemSensorData sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(ThreadInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + ThreadInformationData threadData = (ThreadInformationData) sensorData; + assertThat(threadData.getCount(), is(equalTo(1))); + + // as there was only one data object min/max/total the values must be the + // same + assertThat(threadData.getMinDaemonThreadCount(), is(equalTo(daemonThreadCount))); + assertThat(threadData.getMaxDaemonThreadCount(), is(equalTo(daemonThreadCount))); + assertThat(threadData.getTotalDaemonThreadCount(), is(equalTo(daemonThreadCount))); + + assertThat(threadData.getMinPeakThreadCount(), is(equalTo(peakThreadCount))); + assertThat(threadData.getMaxPeakThreadCount(), is(equalTo(peakThreadCount))); + assertThat(threadData.getTotalPeakThreadCount(), is(equalTo(peakThreadCount))); + + assertThat(threadData.getMinThreadCount(), is(equalTo(threadCount))); + assertThat(threadData.getMaxThreadCount(), is(equalTo(threadCount))); + assertThat(threadData.getTotalThreadCount(), is(equalTo(threadCount))); + + assertThat(threadData.getMinTotalStartedThreadCount(), is(equalTo(totalStartedThreadCount))); + assertThat(threadData.getMaxTotalStartedThreadCount(), is(equalTo(totalStartedThreadCount))); + assertThat(threadData.getTotalTotalStartedThreadCount(), is(equalTo(totalStartedThreadCount))); + } + + @Test + public void twoDataSets() throws IdNotAvailableException { + int daemonThreadCount = 5; + int daemonThreadCount2 = 6; + int threadCount = 13; + int threadCount2 = 15; + int peakThreadCount = 25; + int peakThreadCount2 = 25; + long totalStartedThreadCount = 55L; + long totalStartedThreadCount2 = 60L; + long sensorTypeIdent = 13L; + long platformIdent = 11L; + + when(idManager.getPlatformId()).thenReturn(platformIdent); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenReturn(sensorTypeIdent); + + // ------------------------ + // FIRST UPDATE CALL + // ------------------------ + when(threadBean.getDaemonThreadCount()).thenReturn(daemonThreadCount); + when(threadBean.getThreadCount()).thenReturn(threadCount); + when(threadBean.getPeakThreadCount()).thenReturn(peakThreadCount); + when(threadBean.getTotalStartedThreadCount()).thenReturn(totalStartedThreadCount); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + threadInfo.update(coreService, sensorTypeIdent); + + // -> The service must create a new one and add it to the storage + // We use an argument capturer to further inspect the given argument. + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + SystemSensorData sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(ThreadInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + ThreadInformationData threadData = (ThreadInformationData) sensorData; + assertThat(threadData.getCount(), is(equalTo(1))); + + // as there was only one data object min/max/total values must be the + // same + assertThat(threadData.getMinDaemonThreadCount(), is(equalTo(daemonThreadCount))); + assertThat(threadData.getMaxDaemonThreadCount(), is(equalTo(daemonThreadCount))); + assertThat(threadData.getTotalDaemonThreadCount(), is(equalTo(daemonThreadCount))); + + assertThat(threadData.getMinPeakThreadCount(), is(equalTo(peakThreadCount))); + assertThat(threadData.getMaxPeakThreadCount(), is(equalTo(peakThreadCount))); + assertThat(threadData.getTotalPeakThreadCount(), is(equalTo(peakThreadCount))); + + assertThat(threadData.getMinThreadCount(), is(equalTo(threadCount))); + assertThat(threadData.getMaxThreadCount(), is(equalTo(threadCount))); + assertThat(threadData.getTotalThreadCount(), is(equalTo(threadCount))); + + assertThat(threadData.getMinTotalStartedThreadCount(), is(equalTo(totalStartedThreadCount))); + assertThat(threadData.getMaxTotalStartedThreadCount(), is(equalTo(totalStartedThreadCount))); + assertThat(threadData.getTotalTotalStartedThreadCount(), is(equalTo(totalStartedThreadCount))); + + // ------------------------ + // SECOND UPDATE CALL + // ------------------------ + when(threadBean.getDaemonThreadCount()).thenReturn(daemonThreadCount2); + when(threadBean.getThreadCount()).thenReturn(threadCount2); + when(threadBean.getPeakThreadCount()).thenReturn(peakThreadCount2); + when(threadBean.getTotalStartedThreadCount()).thenReturn(totalStartedThreadCount2); + + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(threadData); + + threadInfo.update(coreService, sensorTypeIdent); + verify(coreService, times(1)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + + sensorData = sensorDataCaptor.getValue(); + assertThat(sensorData, is(instanceOf(ThreadInformationData.class))); + assertThat(sensorData.getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(sensorData.getSensorTypeIdent(), is(equalTo(sensorTypeIdent))); + + threadData = (ThreadInformationData) sensorData; + assertThat(threadData.getCount(), is(equalTo(2))); + + assertThat(threadData.getMinDaemonThreadCount(), is(equalTo(daemonThreadCount))); + assertThat(threadData.getMaxDaemonThreadCount(), is(equalTo(daemonThreadCount2))); + assertThat(threadData.getTotalDaemonThreadCount(), is(equalTo(daemonThreadCount + daemonThreadCount2))); + + assertThat(threadData.getMinPeakThreadCount(), is(equalTo(peakThreadCount))); + assertThat(threadData.getMaxPeakThreadCount(), is(equalTo(peakThreadCount2))); + assertThat(threadData.getTotalPeakThreadCount(), is(equalTo(peakThreadCount + peakThreadCount2))); + + assertThat(threadData.getMinThreadCount(), is(equalTo(threadCount))); + assertThat(threadData.getMaxThreadCount(), is(equalTo(threadCount2))); + assertThat(threadData.getTotalThreadCount(), is(equalTo(threadCount + threadCount2))); + + assertThat(threadData.getMinTotalStartedThreadCount(), is(equalTo(totalStartedThreadCount))); + assertThat(threadData.getMaxTotalStartedThreadCount(), is(equalTo(totalStartedThreadCount2))); + assertThat(threadData.getTotalTotalStartedThreadCount(), is(equalTo(totalStartedThreadCount + totalStartedThreadCount2))); + } + + @Test + public void idNotAvailableTest() throws IdNotAvailableException { + int daemonThreadCount = 5; + int threadCount = 13; + int peakThreadCount = 25; + long totalStartedThreadCount = 55L; + long sensorTypeIdent = 13L; + + when(threadBean.getDaemonThreadCount()).thenReturn(daemonThreadCount); + when(threadBean.getThreadCount()).thenReturn(threadCount); + when(threadBean.getPeakThreadCount()).thenReturn(peakThreadCount); + when(threadBean.getTotalStartedThreadCount()).thenReturn(totalStartedThreadCount); + + when(idManager.getPlatformId()).thenThrow(new IdNotAvailableException("expected")); + when(idManager.getRegisteredSensorTypeId(sensorTypeIdent)).thenThrow(new IdNotAvailableException("expected")); + + // there is no current data object available + when(coreService.getPlatformSensorData(sensorTypeIdent)).thenReturn(null); + threadInfo.update(coreService, sensorTypeIdent); + + ArgumentCaptor sensorDataCaptor = ArgumentCaptor.forClass(SystemSensorData.class); + verify(coreService, times(0)).addPlatformSensorData(eq(sensorTypeIdent), sensorDataCaptor.capture()); + } + + protected Level getLogLevel() { + return Level.FINEST; + } +} diff --git a/Agent/test/info/novatec/inspectit/util/ReflectionCacheTest.java b/Agent/test/info/novatec/inspectit/util/ReflectionCacheTest.java new file mode 100644 index 000000000..42ae0c92c --- /dev/null +++ b/Agent/test/info/novatec/inspectit/util/ReflectionCacheTest.java @@ -0,0 +1,74 @@ +package info.novatec.inspectit.util; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import info.novatec.inspectit.agent.AbstractLogSupport; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class ReflectionCacheTest extends AbstractLogSupport { + + private ReflectionCache cache; + + @BeforeMethod + public void init() { + cache = new ReflectionCache(); + } + + @Test + public void normalUsage() { + String testString = "I am a test"; + + String result = (String) cache.invokeMethod(String.class, "toString", null, testString, null, null); + assertThat(result, is(testString)); + assertThat((int) cache.cache.size(), is(1)); + result = (String) cache.invokeMethod(String.class, "toString", null, testString, null, null); + assertThat(result, is(testString)); + + // check that there is only one cache entry + assertThat((int) cache.cache.size(), is(1)); + } + + @Test + public void methodWithParameter() { + Object errorvalue = "errorvalue"; + String input = "input"; + String concat = "concat"; + String result = (String) cache.invokeMethod(String.class, "concat", new Class[] { String.class }, input, new Object[] { concat }, errorvalue); + assertThat(result, is(equalTo(input.concat(concat)))); + } + + @Test + public void methodIncorrect() { + Object errorvalue = "errorvalue"; + Object result = cache.invokeMethod(String.class, "methodDoesNotExist", null, "test", null, errorvalue); + assertThat(result, is(errorvalue)); + + result = cache.invokeMethod(String.class, "toString", new Class[] { String.class }, "test", null, errorvalue); + assertThat(result, is(errorvalue)); + } + + @Test + public void nullMethod() { + Object errorvalue = "errorvalue"; + Object result = cache.invokeMethod(String.class, null, null, "test", null, errorvalue); + assertThat(result, is(errorvalue)); + } + + @Test + public void nullClass() { + Object errorvalue = "errorvalue"; + Object result = cache.invokeMethod(null, "abc", null, "test", null, errorvalue); + assertThat(result, is(errorvalue)); + } + + @Test + public void nullInstanceGiven() { + Object errorvalue = "errorvalue"; + String result = (String) cache.invokeMethod(String.class, "toString", null, null, null, errorvalue); + assertThat(result, is(errorvalue)); + } +} diff --git a/Agent/test/info/novatec/inspectit/util/StringConstraintTest.java b/Agent/test/info/novatec/inspectit/util/StringConstraintTest.java new file mode 100644 index 000000000..c31b26e92 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/util/StringConstraintTest.java @@ -0,0 +1,184 @@ +package info.novatec.inspectit.util; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isEmptyString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import info.novatec.inspectit.agent.AbstractLogSupport; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang.ObjectUtils; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class StringConstraintTest extends AbstractLogSupport { + + private StringConstraint constraint; + + private Map parameter; + + private int stringLength; + + @BeforeMethod + public void init() { + stringLength = new Random().nextInt(1000) + 1; + parameter = new HashMap(); + parameter.put("stringLength", String.valueOf(stringLength)); + constraint = new StringConstraint(parameter); + } + + @Test + public void stringTooLong() { + String testStr = fillString('x', stringLength + 1); + String resultString = constraint.crop(testStr); + String ending = "..."; + + assertThat(resultString.length(), is(stringLength + ending.length())); + assertThat(resultString, endsWith(ending)); + } + + @Test + public void stringTooLongWithFinalChar() { + char finalChar = '\''; + String testStr = fillString('x', stringLength + 1); + testStr += finalChar; + String resultString = constraint.cropKeepFinalCharacter(testStr, finalChar); + String ending = "..." + finalChar; + + assertThat(resultString.length(), is(stringLength + ending.length())); + assertThat(resultString, endsWith(ending)); + } + + @Test + public void stringShortEnough() { + String testStr = fillString('x', stringLength - 1); + String resultString = constraint.crop(testStr); + + assertThat("Same istances", resultString == testStr); + assertThat(resultString.length(), is(equalTo(testStr.length()))); + } + + @Test + public void stringExactSize() { + String testStr = fillString('x', stringLength); + String resultString = constraint.crop(testStr); + + assertThat(resultString, is(equalTo(testStr))); + assertThat(resultString.length(), is(equalTo(testStr.length()))); + } + + @Test + public void stringLengthOf0() { + parameter.put("stringLength", String.valueOf(0)); + StringConstraint constr = new StringConstraint(parameter); + + String testStr = fillString('x', 100); + String resultString = constr.crop(testStr); + + assertThat(resultString, isEmptyString()); + } + + @Test + public void stringLengthOf0WithFinalChar() { + char finalChar = '\''; + parameter.put("stringLength", String.valueOf(0)); + StringConstraint constr = new StringConstraint(parameter); + + String testStr = finalChar + fillString('x', 50) + finalChar; + String resultString = constr.cropKeepFinalCharacter(testStr, finalChar); + + assertThat(resultString, isEmptyString()); + } + + @Test + public void stringIsNull() { + String testStr = null; + String resultString = constraint.crop(testStr); + + assertThat(resultString, is(nullValue())); + } + + @Test + public void cropStringMapNoCropping() { + + constraint = new StringConstraint(Collections. singletonMap("stringLength", "20")); + + final String param1 = "p1"; + final String param2 = "p2"; + final String param3 = "p3"; + final String param1VReal = "value"; + final String param2VReal1 = "value5"; + final String param2VReal2 = "value6"; + final String param3VReal1 = "value7"; + final String param3VReal2 = "value8"; + final String[] param1V = new String[] { param1VReal }; + final String[] param2V = new String[] { param2VReal1, param2VReal2 }; + final String[] param3V = new String[] { param3VReal1, param3VReal2 }; + final Map parameterMap = new HashMap(); + MapUtils.putAll(parameterMap, new Object[][] { { param1, param1V }, { param2, param2V }, { param3, param3V } }); + + Map result = constraint.crop(parameterMap); + + assertThat(result.size(), is(equalTo(parameterMap.size()))); + assertThat("Same compared by ObjectUtils", ObjectUtils.equals(result, parameterMap)); + assertThat(result.get(param1), is(param1V)); + assertThat(result.get(param2), is(param2V)); + assertThat(result.get(param3), is(param3V)); + assertThat(result.get(param1)[0], is(param1VReal)); + assertThat(result.get(param2)[0], is(param2VReal1)); + assertThat(result.get(param2)[1], is(param2VReal2)); + assertThat(result.get(param3)[0], is(param3VReal1)); + assertThat(result.get(param3)[1], is(param3VReal2)); + } + + @Test + /** Tests whether the first entry is correctly copied to new map */ + public void cropStringMapCropSecondEntry() { + constraint = new StringConstraint(Collections. singletonMap("stringLength", "20")); + + final String param1 = "p1"; + final String param2 = "p2"; + final String param3 = "p3"; + final String param1VReal = "value"; + final String param2VReal1 = "I am really very long and need to be cropped"; + final String param2VReal2 = "value6"; + final String param3VReal1 = "value7"; + final String param3VReal2 = "value8"; + final String[] param1V = new String[] { param1VReal }; + final String[] param2V = new String[] { param2VReal1, param2VReal2 }; + final String[] param3V = new String[] { param3VReal1, param3VReal2 }; + final Map parameterMap = new HashMap(); + MapUtils.putAll(parameterMap, new Object[][] { { param1, param1V }, { param2, param2V }, { param3, param3V } }); + + Map result = constraint.crop(parameterMap); + + assertThat(result.size(), is(equalTo(parameterMap.size()))); + assertThat("Not same by ObjectUtils, need to be cropped", !ObjectUtils.equals(result, parameterMap)); + assertThat(result.get(param1), is(param1V)); + assertThat(result.get(param2), is(not(param2V))); + assertThat(result.get(param3), is(param3V)); + assertThat(result.get(param1)[0], is(param1VReal)); + assertThat(result.get(param2)[0], is(not(param2VReal1))); + assertThat(result.get(param2)[1], is(param2VReal2)); + assertThat(result.get(param3)[0], is(param3VReal1)); + assertThat(result.get(param3)[1], is(param3VReal2)); + } + + private String fillString(char character, int count) { + // creates a string of 'x' repeating characters + char[] chars = new char[count]; + while (count > 0) { + chars[--count] = character; + } + return new String(chars); + } +} diff --git a/Agent/test/info/novatec/inspectit/util/ThreadLocalStackTest.java b/Agent/test/info/novatec/inspectit/util/ThreadLocalStackTest.java new file mode 100644 index 000000000..2a82e0928 --- /dev/null +++ b/Agent/test/info/novatec/inspectit/util/ThreadLocalStackTest.java @@ -0,0 +1,93 @@ +package info.novatec.inspectit.util; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyZeroInteractions; + +import java.util.LinkedList; +import java.util.NoSuchElementException; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class ThreadLocalStackTest { + + private ThreadLocalStack threadLocalStack; + + @BeforeMethod(firstTimeOnly = true) + public void initTestClass() { + threadLocalStack = new ThreadLocalStack(); + } + + @Test + public void emptyStack() { + Object object = threadLocalStack.get(); + + assertThat(object, is(notNullValue())); + assertThat(object, is(instanceOf(LinkedList.class))); + } + + @Test(dependsOnMethods = "emptyStack") + public void oneValue() { + Object object = mock(Object.class); + threadLocalStack.push(object); + + Object returnValue = threadLocalStack.pop(); + + assertThat(returnValue, is(notNullValue())); + assertThat(returnValue, is(object)); + verifyZeroInteractions(object); + } + + @Test(dependsOnMethods = "emptyStack", expectedExceptions = { NoSuchElementException.class }) + public void noSuchElement() { + threadLocalStack.pop(); + } + + @Test(dependsOnMethods = "emptyStack", invocationCount = 10, threadPoolSize = 10) + public void stackTest() { + Object objectOne = mock(Object.class); + Object objectTwo = mock(Object.class); + Object objectThree = mock(Object.class); + + threadLocalStack.push(objectOne); + threadLocalStack.push(objectTwo); + threadLocalStack.push(objectThree); + + Object returnValueOne = threadLocalStack.pop(); + Object returnValueTwo = threadLocalStack.pop(); + Object returnValueThree = threadLocalStack.pop(); + + assertThat(returnValueOne, is(objectThree)); + assertThat(returnValueTwo, is(objectTwo)); + assertThat(returnValueThree, is(objectOne)); + + verifyZeroInteractions(objectOne); + verifyZeroInteractions(objectTwo); + verifyZeroInteractions(objectThree); + } + + @Test(dependsOnMethods = "emptyStack") + public void getAndRemoveFirst() { + Object objectOne = mock(Object.class); + Object objectTwo = mock(Object.class); + Object objectThree = mock(Object.class); + + threadLocalStack.push(objectOne); + threadLocalStack.push(objectTwo); + threadLocalStack.push(objectThree); + + assertThat(threadLocalStack.getAndRemoveFirst(), is(objectOne)); + assertThat(threadLocalStack.getAndRemoveFirst(), is(objectTwo)); + assertThat(threadLocalStack.getAndRemoveFirst(), is(objectThree)); + + verifyZeroInteractions(objectOne); + verifyZeroInteractions(objectTwo); + verifyZeroInteractions(objectThree); + } + +} diff --git a/Agent/test/info/novatec/inspectit/util/WeakListTest.java b/Agent/test/info/novatec/inspectit/util/WeakListTest.java new file mode 100644 index 000000000..00a7ae45d --- /dev/null +++ b/Agent/test/info/novatec/inspectit/util/WeakListTest.java @@ -0,0 +1,102 @@ +package info.novatec.inspectit.util; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class WeakListTest { + + private WeakList weakList; + + @BeforeMethod + public void initTestClass() { + weakList = new WeakList(); + } + + @Test + public void oneElement() { + Object object = new Object(); + weakList.add(object); + Object returnValue = weakList.get(0); + + assertThat(returnValue, is(notNullValue())); + assertThat(returnValue, is(object)); + } + + @Test + public void clearNoGC() { + Object objectOne = new Object(); + Object objectTwo = new Object(); + Object objectThree = new Object(); + + weakList.add(objectOne); + weakList.add(objectTwo); + weakList.add(objectThree); + + weakList.clear(); + + assertThat(weakList, is(empty())); + } + + @Test + public void clearWithGC() { + Object objectOne = new Object(); + Object objectTwo = new Object(); + Object objectThree = new Object(); + + weakList.add(objectOne); + weakList.add(objectTwo); + weakList.add(objectThree); + + objectOne = null; + objectTwo = null; + objectThree = null; + + System.gc(); + + weakList.clear(); + + assertThat(weakList, is(empty())); + } + + @Test + public void containsNoGC() { + Object objectOne = new Object(); + Object objectTwo = new Object(); + + weakList.add(objectOne); + weakList.add(objectTwo); + + objectOne = null; + + assertThat(weakList, hasItem(objectTwo)); + } + + @Test + public void containsWithGC() { + Object objectOne = new Object(); + Object objectTwo = new Object(); + + weakList.add(objectOne); + weakList.add(objectTwo); + + objectOne = null; + + System.gc(); + + assertThat(weakList, hasItem(objectTwo)); + } + + @Test(expectedExceptions = { IndexOutOfBoundsException.class }) + public void outOfBounds() { + assertThat(weakList.get(5), is(nullValue())); + } + +} diff --git a/CMR/.checkstyle b/CMR/.checkstyle new file mode 100644 index 000000000..90e703e66 --- /dev/null +++ b/CMR/.checkstyle @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/CMR/.classpath b/CMR/.classpath new file mode 100644 index 000000000..8f51cf892 --- /dev/null +++ b/CMR/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/CMR/.gitignore b/CMR/.gitignore new file mode 100644 index 000000000..dd6e3f335 --- /dev/null +++ b/CMR/.gitignore @@ -0,0 +1,18 @@ +/bin +/dist +/lib +/build +/generatedsrc +/test-output +/logs +/invocations +/db +temp-testng-customsuite.xml +/release +/resources/ivy/install +/license/license.lic +resources/jvm +/reports +/storage +/config/configurationUpdates*.xml +/config/configurationUpdates.xml* diff --git a/CMR/.pmd b/CMR/.pmd new file mode 100644 index 000000000..863bff9e8 --- /dev/null +++ b/CMR/.pmd @@ -0,0 +1,7 @@ + + + true + shared/config/pmd/pmd_rules.xml + false + false + diff --git a/CMR/.project b/CMR/.project new file mode 100644 index 000000000..02fcd8f27 --- /dev/null +++ b/CMR/.project @@ -0,0 +1,43 @@ + + + CMR + + + + + + org.eclipse.jdt.core.javabuilder + + + + + net.sourceforge.pmd.eclipse.plugin.pmdBuilder + + + + + net.sf.eclipsecs.core.CheckstyleBuilder + + + + + + org.eclipse.jdt.core.javanature + net.sourceforge.pmd.eclipse.plugin.pmdNature + net.sf.eclipsecs.core.CheckstyleNature + org.apache.ivyde.eclipse.ivynature + + + + shared + 2 + shared + + + + + shared + $%7BPARENT-1-PROJECT_LOC%7D/Commons/resources/shared + + + diff --git a/CMR/.settings/org.apache.ivyde.eclipse.prefs b/CMR/.settings/org.apache.ivyde.eclipse.prefs new file mode 100644 index 000000000..6ef82e919 --- /dev/null +++ b/CMR/.settings/org.apache.ivyde.eclipse.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.apache.ivyde.eclipse.standaloneretrieve= diff --git a/CMR/.settings/org.eclipse.jdt.core.prefs b/CMR/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..24c840f73 --- /dev/null +++ b/CMR/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,13 @@ +eclipse.preferences.version=1 +instance/org.eclipse.core.net/org.eclipse.core.net.hasMigrated=true +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/CMR/.settings/org.eclipse.jdt.ui.prefs b/CMR/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 000000000..146df85e9 --- /dev/null +++ b/CMR/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,4 @@ +#Tue Feb 05 14:16:54 CET 2008 +eclipse.preferences.version=1 +internal.default.compliance=default +org.eclipse.jdt.ui.text.custom_code_templates= diff --git a/CMR/build.properties b/CMR/build.properties new file mode 100644 index 000000000..79efd1e5c --- /dev/null +++ b/CMR/build.properties @@ -0,0 +1,54 @@ +dist.jar.name=inspectit-cmr.jar +module.version.target=0.1 + +## Ivy +ivy.file = ${basedir}/resources/ivy/ivy.xml + +## Settings for TestNG +resources.testng=${basedir}/resources/testng + +## Some general settings +build.root=${basedir}/build +build.classes.root=${build.root}/classes +build.release.root=${build.root}/release +build.release.root.cmr=${build.release.root}/CMR +build.qa.root=${build.root}/QA +build.qa.test=${build.qa.root}/functional_tests +build.qa.test.testdata=${build.qa.test}/testdata +build.qa.test.coveragedata=${build.qa.test}/coveragedata +build.qa.analysis=${build.qa.root}/static_analysis +build.qa.analysis.pmd=${build.qa.analysis}/pmd +build.qa.analysis.findbugs=${build.qa.analysis}/findbugs +build.qa.analysis.checkstyle=${build.qa.analysis}/checkstyle +build.qa.analysis.cpd=${build.qa.analysis}/cpd +build.scripts=${build.root}/scripts +build.test.classes=${build.classes.root}/cmr/test + +## Settings for CMR +build.common-targets.file=${commons.basedir}/resources/shared/build/common-targets.xml +build.cmr.classes=${build.classes.root}/cmr/classes +build.instrumented.classes=${build.classes.root}/cmr/instrumented +src.root=${basedir}/src +test.root=${basedir}/test +release.root=${basedir}/release + +## Setting for CommonsCS +src.commonscs=${basedir}/../CommonsCS/src +commonscs.basedir=${basedir}/../CommonsCS +commonscs.build.release=${commonscs.basedir}/build/release +build.commonscs.classes=${commonscs.basedir}/build/classes/commonscs/classes +build.commonscs.file=${commonscs.basedir}/resources/build.xml +ivy.file.commonscs=${commonscs.basedir}/resources/ivy/ivy.xml + +## Setting for Commons +src.commons=${basedir}/../Commons/src +commons.basedir=${basedir}/../Commons +commons.build.release=${commons.basedir}/build/release +build.commons.classes=${commons.basedir}/build/classes/commons/classes +build.commons.file=${commons.basedir}/resources/build.xml +ivy.file.commons=${commons.basedir}/resources/ivy/ivy.xml +crm.startup.properties=${commons.basedir}/resources/shared/build/cmr-startup.properties + +## Settings for JVM installations +jvm.root=${basedir}/resources/jvm +jvm.list=jre7-linux-x64,jre7-linux-x86,jre7-windows-x86,jre7-windows-x64 diff --git a/CMR/build.xml b/CMR/build.xml new file mode 100644 index 000000000..476db11bb --- /dev/null +++ b/CMR/build.xml @@ -0,0 +1,360 @@ + + + + + Sophisticated Monitoring tool by NovaTec GmbH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Using Classmexer on path: ${string.path.classmexer} + + diff --git a/CMR/config/default.xml b/CMR/config/default.xml new file mode 100644 index 000000000..6faf8154a --- /dev/null +++ b/CMR/config/default.xml @@ -0,0 +1,318 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/CMR/config/schema/configurationSchema.xsd b/CMR/config/schema/configurationSchema.xsd new file mode 100644 index 000000000..488a47a20 --- /dev/null +++ b/CMR/config/schema/configurationSchema.xsd @@ -0,0 +1,265 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CMR/config/schema/configurationUpdateSchema.xsd b/CMR/config/schema/configurationUpdateSchema.xsd new file mode 100644 index 000000000..34720d744 --- /dev/null +++ b/CMR/config/schema/configurationUpdateSchema.xsd @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CMR/logging-config.xml b/CMR/logging-config.xml new file mode 100644 index 000000000..a471bf14c --- /dev/null +++ b/CMR/logging-config.xml @@ -0,0 +1,146 @@ + + + + + + + System.out + + %d{ISO8601}: %-6r [%15.15t] %-5p %30.30c - %m%n%rEx + + + + + + + logs/statistics.log + + %d{ISO8601}: %m%n + + + logs/statistics.%d{yyyy-MM-dd}.%i.zip + 30 + + 20MB + + + + + + + + logs/cmr.log + + %d{ISO8601}: %-6r [%15.15t] %-5p %30.30c - %m%n%rEx + + + logs/cmr.%d{yyyy-MM-dd}.%i.zip + 30 + + 20MB + + + + + + + + + logs/exceptions.log + + %d{ISO8601}: %-6r [%15.15t] %-5p %30.30c - %m%n%rEx + + + logs/exceptions.%d{yyyy-MM-dd}.%i.zip + 30 + + 20MB + + + + WARN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/monitoring/config/common/exclude-classes.cfg b/CMR/monitoring/config/common/exclude-classes.cfg new file mode 100644 index 000000000..7b57e01c1 --- /dev/null +++ b/CMR/monitoring/config/common/exclude-classes.cfg @@ -0,0 +1,21 @@ +## Exclude classes definition +############################# +## Only change the already specified patterns if you are a expert level user +## Add additional classes if needed +############################################################################ + +# note that we do not exclude inspectIT classes here as we want to see them. +#exclude-class info.novatec.inspectit.* +exclude-class *$Proxy* +exclude-class sun.* +exclude-class java.lang.ThreadLocal +exclude-class java.lang.ref.Reference +exclude-class *_WLStub +exclude-class *[] + +# Exclude CGLIB generated classes (see #INSPECTIT-1182) +# CGLIB creates very special bytecode structures that often leads to problems with bytecode modification frameworks +# in addition the generated classes are usually not interesting for monitoring +# Workaround: If you want to monitor these classes nonetheless you can try starting your JVM with the option -Xverify:none to +# suppress any warning regarding potentially invalid bytecode +exclude-class *CGLIB$$* \ No newline at end of file diff --git a/CMR/monitoring/config/common/sql-parameters.cfg b/CMR/monitoring/config/common/sql-parameters.cfg new file mode 100644 index 000000000..08d45805c --- /dev/null +++ b/CMR/monitoring/config/common/sql-parameters.cfg @@ -0,0 +1,89 @@ +## SQL Parameter +####################### +# SQL Prepared Statement Parameter Replacement +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setArray(int,java.sql.Array) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setAsciiStream(int,java.io.InputStream) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setAsciiStream(int,java.io.InputStream,int) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setAsciiStream(int,java.io.InputStream,long) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBigDecimal(int,java.math.BigDecimal) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBinaryStream(int,java.io.InputStream) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBinaryStream(int,java.io.InputStream,int) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBinaryStream(int,java.io.InputStream,long) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBlob(int,java.sql.Blob) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBlob(int,java.io.InputStream) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBlob(int,java.io.InputStream,long) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBoolean(int,boolean) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setByte(int,byte) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setBytes(int,byte[]) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setCharacterStream(int,java.io.Reader) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setCharacterStream(int,java.io.Reader,int) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setCharacterStream(int,java.io.Reader,long) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setClob(int,java.sql.Clob) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setClob(int,java.io.Reader) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setClob(int,java.io.Reader,long) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setDate(int,java.sql.Date) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setDate(int,java.sql.Date,java.util.Calendar) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setDouble(int,double) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setFloat(int,float) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setInt(int,int) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setLong(int,long) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNCharacterStream(int,java.io.Reader) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNCharacterStream(int,java.io.Reader,long) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNClob(int,java.sql.NClob) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNClob(int,java.io.Reader) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNClob(int,java.io.Reader,long) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNClob(int,java.sql.NClob) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNString(int,java.lang.String) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setObject(int,java.lang.Object) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setObject(int,java.lang.Object,int) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setObject(int,java.lang.Object,int,int) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setRef(int,java.sql.Ref) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setRowId(int,java.sql.RowId) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setShort(int,short) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setSQLXML(int,java.sql.SQLXML) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setString(int,java.lang.String) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setTime(int,java.sql.Time) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setTime(int,java.sql.Time,java.util.Calendar) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setTimestamp(int,java.sql.Timestamp) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setTimestamp(int,java.sql.Timestamp,java.util.Calendar) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setUnicodeStream(int,java.io.InputStream,int) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setURL(int,java.net.URL) interface=true + +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNull(int,int) interface=true +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement setNull(int,int,java.lang.String) interface=true + +sensor jdbc-prepared-statement-parameter java.sql.PreparedStatement clearParameters() interface=true + +# Postgre SQL Prepared Statement Parameter Replacement +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setArray(int,java.sql.Array) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setAsciiStream(int,java.io.InputStream,int) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setBigDecimal(int,java.math.BigDecimal) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setBinaryStream(int,java.io.InputStream,int) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setBlob(int,java.sql.Blob) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setBoolean(int,boolean) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setByte(int,byte) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setBytes(int,byte[]) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setCharacterStream(int,java.io.Reader,int) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setClob(int,java.sql.Clob) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setDate(int,java.sql.Date) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setDate(int,java.sql.Date,java.util.Calendar) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setDouble(int,double) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setFloat(int,float) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setInt(int,int) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setLong(int,long) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setObject(int,java.lang.Object) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setObject(int,java.lang.Object,int) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setObject(int,java.lang.Object,int,int) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setRef(int,java.sql.Ref) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setShort(int,short) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setString(int,java.lang.String) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setTime(int,java.sql.Time) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setTime(int,java.sql.Time,java.util.Calendar) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setTimestamp(int,java.sql.Timestamp) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setTimestamp(int,java.sql.Timestamp,java.util.Calendar) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setUnicodeStream(int,java.io.InputStream,int) + +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setNull(int,int) +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement setNull(int,int,java.lang.String) + +sensor jdbc-prepared-statement-parameter org.postgresql.jdbc2.AbstractJdbc2Statement clearParameters() \ No newline at end of file diff --git a/CMR/monitoring/config/common/sql.cfg b/CMR/monitoring/config/common/sql.cfg new file mode 100644 index 000000000..388c4d86f --- /dev/null +++ b/CMR/monitoring/config/common/sql.cfg @@ -0,0 +1,52 @@ +## SQL Tracing (generic) +######################## +# SQL Connection +sensor jdbc-connection java.sql.Connection prepareStatement(java.lang.String) interface=true +sensor jdbc-connection java.sql.Connection prepareStatement(java.lang.String,int) interface=true +sensor jdbc-connection java.sql.Connection prepareStatement(java.lang.String,int[]) interface=true +sensor jdbc-connection java.sql.Connection prepareStatement(java.lang.String,int,int) interface=true +sensor jdbc-connection java.sql.Connection prepareStatement(java.lang.String,int,int,int) interface=true +sensor jdbc-connection java.sql.Connection prepareStatement(java.lang.String,java.lang.String[]) interface=true +sensor jdbc-connection java.sql.Connection prepareCall(java.lang.String) interface=true +sensor jdbc-connection java.sql.Connection prepareCall(java.lang.String,int,int) interface=true +sensor jdbc-connection java.sql.Connection prepareCall(java.lang.String,int,int,int) interface=true +# WebLogic SQL Connection +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareStatement(java.lang.String) interface=true +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareStatement(java.lang.String,int) interface=true +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareStatement(java.lang.String,int[]) interface=true +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareStatement(java.lang.String,int,int) interface=true +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareStatement(java.lang.String,int,int,int) interface=true +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareStatement(java.lang.String,java.lang.String[]) interface=true +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareCall(java.lang.String) interface=true +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareCall(java.lang.String,int,int) interface=true +sensor jdbc-connection weblogic.jdbc.wrapper.Connection prepareCall(java.lang.String,int,int,int) interface=true +# SQL Prepared Statement +sensor jdbc-prepared-statement java.sql.PreparedStatement interface=true +sensor jdbc-prepared-statement java.sql.PreparedStatement executeQuery() interface=true +sensor jdbc-prepared-statement java.sql.PreparedStatement executeUpdate() interface=true +sensor jdbc-prepared-statement java.sql.PreparedStatement execute() interface=true +sensor jdbc-prepared-statement java.sql.Statement executeBatch() interface=true +# PostgreSQL Prepared Statement +sensor jdbc-prepared-statement org.postgresql.jdbc2.AbstractJdbc2Statement executeQuery() +sensor jdbc-prepared-statement org.postgresql.jdbc2.AbstractJdbc2Statement executeUpdate() +sensor jdbc-prepared-statement org.postgresql.jdbc2.AbstractJdbc2Statement execute() +# SQL Statement +sensor jdbc-statement java.sql.Statement execute(java.lang.String) interface=true +sensor jdbc-statement java.sql.Statement execute(java.lang.String,int) interface=true +sensor jdbc-statement java.sql.Statement execute(java.lang.String,int[]) interface=true +sensor jdbc-statement java.sql.Statement execute(java.lang.String,java.lang.String[]) interface=true +sensor jdbc-statement java.sql.Statement executeUpdate(java.lang.String)interface=true +sensor jdbc-statement java.sql.Statement executeUpdate(java.lang.String,int) interface=true +sensor jdbc-statement java.sql.Statement executeUpdate(java.lang.String,int[]) interface=true +sensor jdbc-statement java.sql.Statement executeUpdate(java.lang.String,java.lang.String[]) interface=true +sensor jdbc-statement java.sql.Statement executeQuery(java.lang.String) interface=true +# H2 +sensor jdbc-connection org.h2.jdbc.JdbcConnection prepareAutoCloseStatement(java.lang.String) +# WebSphere +sensor jdbc-connection com.ibm.db2.jcc.t4.b c(java.lang.String,int,int,int) +sensor jdbc-connection com.ibm.icm.da.portable.common.sql.DefaultPConnection prepareStatement1(java.lang.String) +# Derby metadata queries +sensor jdbc-connection org.apache.derby.impl.jdbc.EmbedConnection prepareMetaDataStatement(java.lang.String) + +# Exclude classes that are not meaningful +exclude-class oracle.jdbc.driver.OracleClosedStatement \ No newline at end of file diff --git a/CMR/monitoring/config/inspectit-agent.cfg b/CMR/monitoring/config/inspectit-agent.cfg new file mode 100644 index 000000000..27d0a17e8 --- /dev/null +++ b/CMR/monitoring/config/inspectit-agent.cfg @@ -0,0 +1,87 @@ +## repository +############################################# +repository localhost 9070 Development/CMR + +## method-sensor-type [] +##################################################################################### +# method-sensor-type average-timer info.novatec.inspectit.agent.sensor.method.averagetimer.AverageTimerSensor HIGH stringLength=100 +method-sensor-type timer info.novatec.inspectit.agent.sensor.method.timer.TimerSensor MAX stringLength=100 +method-sensor-type isequence info.novatec.inspectit.agent.sensor.method.invocationsequence.InvocationSequenceSensor INVOC stringLength=100 +method-sensor-type jdbc-connection info.novatec.inspectit.agent.sensor.method.jdbc.ConnectionSensor MIN +method-sensor-type jdbc-prepared-statement info.novatec.inspectit.agent.sensor.method.jdbc.PreparedStatementSensor MIN +method-sensor-type jdbc-prepared-statement-parameter info.novatec.inspectit.agent.sensor.method.jdbc.PreparedStatementParameterSensor MIN +method-sensor-type jdbc-statement info.novatec.inspectit.agent.sensor.method.jdbc.StatementSensor MIN + +## exception-sensor-type [] +###################################################################### +exception-sensor-type info.novatec.inspectit.agent.sensor.exception.ExceptionSensor mode=simple + +## platform-sensor-type [] +##################################################################### +platform-sensor-type info.novatec.inspectit.agent.sensor.platform.ClassLoadingInformation +platform-sensor-type info.novatec.inspectit.agent.sensor.platform.CompilationInformation +platform-sensor-type info.novatec.inspectit.agent.sensor.platform.MemoryInformation +platform-sensor-type info.novatec.inspectit.agent.sensor.platform.CpuInformation +platform-sensor-type info.novatec.inspectit.agent.sensor.platform.RuntimeInformation +platform-sensor-type info.novatec.inspectit.agent.sensor.platform.SystemInformation +platform-sensor-type info.novatec.inspectit.agent.sensor.platform.ThreadInformation + +## send-strategy +####################################### +send-strategy info.novatec.inspectit.agent.sending.impl.TimeStrategy time=5000 +# send-strategy info.novatec.inspectit.agent.sending.impl.ListSizeStrategy size=10 + +## buffer-strategy +######################################### +buffer-strategy info.novatec.inspectit.agent.buffer.impl.SimpleBufferStrategy +#buffer-strategy info.novatec.inspectit.agent.buffer.impl.SizeBufferStrategy size=12 + +## Ignore classes settings +######################################### +$include common/exclude-classes.cfg + +## SQL tracing +############## +$include common/sql.cfg +## Uncomment configuration file under to enable parameter binding for SQL queries. This feature allows to have +## prepared Statements filled with the concrete parameter value that it was executed with, instead of just "?" values. +## Bear in mind that activating this feature will affect performance in a negative way as more methods need to be instrumented. +$include common/sql-parameters.cfg + +# All services are proxies +sensor isequence info.novatec.inspectit.cmr.service* * modifiers=pub interface=true +sensor timer info.novatec.inspectit.cmr.service* * modifiers=pub interface=true + +# All DAOs +sensor timer info.novatec.inspectit.cmr.dao.* * modifiers=pub interface=true + +# Buffer +sensor timer info.novatec.inspectit.cmr.cache.IBuffer put interface=true +sensor timer info.novatec.inspectit.cmr.cache.IBuffer evict interface=true +sensor timer info.novatec.inspectit.cmr.cache.IBuffer analyzeNext interface=true +sensor timer info.novatec.inspectit.cmr.cache.IBuffer indexNext interface=true + +# Storage +sensor timer info.novatec.inspectit.storage.StorageManager * modifiers=pub superclass=true +sensor timer info.novatec.inspectit.storage.StorageWriter * modifiers=pub superclass=true +sensor timer info.novatec.inspectit.cmr.storage.CmrStorageRecorder stop* +sensor timer info.novatec.inspectit.cmr.storage.CmrStorageRecorder start* +sensor timer info.novatec.inspectit.cmr.storage.CmrStorageRecorder record + +# Indexing +sensor timer info.novatec.inspectit.indexing.ITreeComponent query* interface=true +sensor timer info.novatec.inspectit.indexing.AbstractBranch query* +sensor timer info.novatec.inspectit.indexing.buffer.IBufferTreeComponent cleanWithRunnable interface=true + +# Aggregation +sensor timer info.novatec.inspectit.indexing.aggregation.impl.AggregationPerformer processCollection modifiers=pub + +# NIO +sensor timer info.novatec.inspectit.storage.nio.write.WritingChannelManager write* +sensor timer info.novatec.inspectit.storage.nio.read.ReadingChannelManager read* + +# File upload servlet +sensor timer info.novatec.inspectit.cmr.jetty.FileUploadServlet doGet + +# All our exceptions +exception-sensor info.novatec.inspectit* \ No newline at end of file diff --git a/CMR/monitoring/config/logging.properties b/CMR/monitoring/config/logging.properties new file mode 100644 index 000000000..9cf6de206 --- /dev/null +++ b/CMR/monitoring/config/logging.properties @@ -0,0 +1,17 @@ +# This is the default logging configuration for the inspectIT agent. Feel free +# to adapt this logging to your needs. +# +# To integrate this logging configuration set the "-Djava.util.logging.config.file" Parameter to +# point to the configuration (for example: -Djava.util.logging.config.file=[path-to]/logging.properties + + +# The console handler logs output to the console +handlers= java.util.logging.ConsoleHandler + +# In addition to that additional handlers to store the information to a file can be added +#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler + +# Default log level - Change this to "fine" for more information or to "severe" to only get errors +.level= INFO + +java.util.logging.ConsoleHandler.formatter = info.novatec.inspectit.util.MessageFormatFormatter \ No newline at end of file diff --git a/CMR/resources/ivy/ivy.xml b/CMR/resources/ivy/ivy.xml new file mode 100644 index 000000000..7679e6916 --- /dev/null +++ b/CMR/resources/ivy/ivy.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CMR/resources/launch/CMR with Agent.launch b/CMR/resources/launch/CMR with Agent.launch new file mode 100644 index 000000000..fc98a859e --- /dev/null +++ b/CMR/resources/launch/CMR with Agent.launch @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/CMR/resources/launch/CMR.launch b/CMR/resources/launch/CMR.launch new file mode 100644 index 000000000..729ec453e --- /dev/null +++ b/CMR/resources/launch/CMR.launch @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/CMR/resources/testng/testng.xml b/CMR/resources/testng/testng.xml new file mode 100644 index 000000000..8fffbd555 --- /dev/null +++ b/CMR/resources/testng/testng.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/beanRefContext.xml b/CMR/src/beanRefContext.xml new file mode 100644 index 000000000..e0b52b247 --- /dev/null +++ b/CMR/src/beanRefContext.xml @@ -0,0 +1,16 @@ + + + + + + + spring/spring-context-global.xml + spring/spring-context-database.xml + spring/spring-context-beans.xml + spring/spring-context-jetty.xml + spring/spring-context-processors.xml + + + + + diff --git a/CMR/src/hibernate/ClassLoadingInformationData.hbm.xml b/CMR/src/hibernate/ClassLoadingInformationData.hbm.xml new file mode 100644 index 000000000..93f88ee41 --- /dev/null +++ b/CMR/src/hibernate/ClassLoadingInformationData.hbm.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/hibernate/CompilationInformationData.hbm.xml b/CMR/src/hibernate/CompilationInformationData.hbm.xml new file mode 100644 index 000000000..768155271 --- /dev/null +++ b/CMR/src/hibernate/CompilationInformationData.hbm.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/hibernate/CpuInformationData.hbm.xml b/CMR/src/hibernate/CpuInformationData.hbm.xml new file mode 100644 index 000000000..e8219ce9c --- /dev/null +++ b/CMR/src/hibernate/CpuInformationData.hbm.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/hibernate/DefaultData.hbm.xml b/CMR/src/hibernate/DefaultData.hbm.xml new file mode 100644 index 000000000..150e80e0c --- /dev/null +++ b/CMR/src/hibernate/DefaultData.hbm.xml @@ -0,0 +1,15 @@ + + + + + + + 1000 + + + + + + + + \ No newline at end of file diff --git a/CMR/src/hibernate/HttpTimerData.hbm.xml b/CMR/src/hibernate/HttpTimerData.hbm.xml new file mode 100644 index 000000000..ca69c2d0b --- /dev/null +++ b/CMR/src/hibernate/HttpTimerData.hbm.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/hibernate/MemoryInformationData.hbm.xml b/CMR/src/hibernate/MemoryInformationData.hbm.xml new file mode 100644 index 000000000..d81147c05 --- /dev/null +++ b/CMR/src/hibernate/MemoryInformationData.hbm.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/hibernate/MethodIdent.hbm.xml b/CMR/src/hibernate/MethodIdent.hbm.xml new file mode 100644 index 000000000..106ea4a32 --- /dev/null +++ b/CMR/src/hibernate/MethodIdent.hbm.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/hibernate/MethodIdentToSensorType.hbm.xml b/CMR/src/hibernate/MethodIdentToSensorType.hbm.xml new file mode 100644 index 000000000..9751d6af1 --- /dev/null +++ b/CMR/src/hibernate/MethodIdentToSensorType.hbm.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/hibernate/MethodSensorData.hbm.xml b/CMR/src/hibernate/MethodSensorData.hbm.xml new file mode 100644 index 000000000..f177bd4b6 --- /dev/null +++ b/CMR/src/hibernate/MethodSensorData.hbm.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/hibernate/ParameterContentData.hbm.xml b/CMR/src/hibernate/ParameterContentData.hbm.xml new file mode 100644 index 000000000..08d5fdd83 --- /dev/null +++ b/CMR/src/hibernate/ParameterContentData.hbm.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/hibernate/PlatformIdent.hbm.xml b/CMR/src/hibernate/PlatformIdent.hbm.xml new file mode 100644 index 000000000..c92351bc7 --- /dev/null +++ b/CMR/src/hibernate/PlatformIdent.hbm.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/hibernate/RuntimeInformationData.hbm.xml b/CMR/src/hibernate/RuntimeInformationData.hbm.xml new file mode 100644 index 000000000..5aa9a0a63 --- /dev/null +++ b/CMR/src/hibernate/RuntimeInformationData.hbm.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/hibernate/SensorTypeIdent.hbm.xml b/CMR/src/hibernate/SensorTypeIdent.hbm.xml new file mode 100644 index 000000000..1d4e32402 --- /dev/null +++ b/CMR/src/hibernate/SensorTypeIdent.hbm.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/hibernate/StorageLabel.hbm.xml b/CMR/src/hibernate/StorageLabel.hbm.xml new file mode 100644 index 000000000..6d57b7a1a --- /dev/null +++ b/CMR/src/hibernate/StorageLabel.hbm.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CMR/src/hibernate/StorageLabelType.hbm.xml b/CMR/src/hibernate/StorageLabelType.hbm.xml new file mode 100644 index 000000000..9f6e5ebba --- /dev/null +++ b/CMR/src/hibernate/StorageLabelType.hbm.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CMR/src/hibernate/SystemInformationData.hbm.xml b/CMR/src/hibernate/SystemInformationData.hbm.xml new file mode 100644 index 000000000..2934c515a --- /dev/null +++ b/CMR/src/hibernate/SystemInformationData.hbm.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/hibernate/SystemSensorData.hbm.xml b/CMR/src/hibernate/SystemSensorData.hbm.xml new file mode 100644 index 000000000..75a3fa76f --- /dev/null +++ b/CMR/src/hibernate/SystemSensorData.hbm.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/CMR/src/hibernate/ThreadInformationData.hbm.xml b/CMR/src/hibernate/ThreadInformationData.hbm.xml new file mode 100644 index 000000000..85e77e4b6 --- /dev/null +++ b/CMR/src/hibernate/ThreadInformationData.hbm.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/hibernate/TimerData.hbm.xml b/CMR/src/hibernate/TimerData.hbm.xml new file mode 100644 index 000000000..909786869 --- /dev/null +++ b/CMR/src/hibernate/TimerData.hbm.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/hibernate/VMArgumentData.hbm.xml b/CMR/src/hibernate/VMArgumentData.hbm.xml new file mode 100644 index 000000000..deba2bcd9 --- /dev/null +++ b/CMR/src/hibernate/VMArgumentData.hbm.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/info/novatec/inspectit/cmr/CMR.java b/CMR/src/info/novatec/inspectit/cmr/CMR.java new file mode 100644 index 000000000..ebbeb590c --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/CMR.java @@ -0,0 +1,222 @@ +package info.novatec.inspectit.cmr; + +import info.novatec.inspectit.cmr.util.Converter; +import info.novatec.inspectit.versioning.IVersioningService; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +import org.apache.commons.lang.SystemUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.access.BeanFactoryLocator; +import org.springframework.beans.factory.access.BeanFactoryReference; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.access.ContextSingletonBeanFactoryLocator; + +import uk.org.lidalia.sysoutslf4j.context.SysOutOverSLF4J; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.joran.spi.JoranException; +import ch.qos.logback.core.util.StatusPrinter; + +/** + * Main class of the Central Measurement Repository. The main method is used to start the + * application. + * + * @author Patrice Bouillet + * + */ +public final class CMR { + + /** + * The logger of this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(CMR.class); + + /** + * Default name of the log file. + */ + public static final String DEFAULT_LOG_FILE_NAME = "logging-config.xml"; + + /** + * JVM property for the log file location. + */ + private static final String LOG_FILE_PROPERTY = "inspectit.logging.config"; + + /** + * The spring bean factory to get the registered beans. + */ + private static BeanFactory beanFactory; // NOPMD + + /** + * startedAsService holds the value if CMR was started as a Windows Service. + */ + private static boolean startedAsService; + + /** + * This class will start the Repository. + */ + private CMR() { + } + + /** + * Pseudo main method of class. + */ + private static void startCMR() { + initLogger(); + + long startTime = System.nanoTime(); + LOGGER.info("Central Measurement Repository is starting up!"); + LOGGER.info("=============================================="); + + startRepository(); + + LOGGER.info("CMR started in " + Converter.nanoToMilliseconds(System.nanoTime() - startTime) + " ms"); + } + + /** + * This class will start the Repository. + */ + private static void startRepository() { + LOGGER.info("Initializing Spring..."); + + BeanFactoryLocator beanFactoryLocator = ContextSingletonBeanFactoryLocator.getInstance(); + BeanFactoryReference beanFactoryReference = beanFactoryLocator.useBeanFactory("ctx"); + beanFactory = beanFactoryReference.getFactory(); + + if (beanFactory instanceof ConfigurableApplicationContext) { + ((ConfigurableApplicationContext) beanFactory).registerShutdownHook(); + } + + LOGGER.info("Spring successfully initialized"); + + if (LOGGER.isInfoEnabled()) { + IVersioningService versioning = (IVersioningService) getBeanFactory().getBean("versioning"); + String currentVersion = "n/a"; + try { + currentVersion = versioning.getVersion(); + } catch (IOException e) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Versioning information could not be read"); + } + } + LOGGER.info("Starting CMR in version " + currentVersion); + } + } + + /** + * Start function. Needed by Procrun. + * + * @param args + * The arguments. + */ + public static void start(String[] args) { + startedAsService = true; + startCMR(); + } + + /** + * Stop function. Needed by Procrun. + * + * @param args + * The arguments. + */ + public static void stop(String[] args) { + System.exit(0); + } + + /** + * Initializes the logger. + */ + private static void initLogger() { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + context.reset(); + + InputStream is = null; + + try { + // first check if it's supplied as parameter + String logFileLocation = System.getProperty(LOG_FILE_PROPERTY); + if (null != logFileLocation) { + Path logPath = Paths.get(logFileLocation).toAbsolutePath(); + if (Files.exists(logPath)) { + is = Files.newInputStream(logPath, StandardOpenOption.READ); + } + } + + // then fail to default if none is specified + if (null == is) { + Path logPath = Paths.get(DEFAULT_LOG_FILE_NAME).toAbsolutePath(); + if (Files.exists(logPath)) { + is = Files.newInputStream(logPath, StandardOpenOption.READ); + } + } + + if (null != is) { + try { + configurator.doConfigure(is); + } catch (JoranException e) { // NOPMD NOCHK StatusPrinter will handle this + } finally { + is.close(); + } + } + } catch (IOException e) { // NOPMD NOCHK StatusPrinter will handle this + } + + StatusPrinter.printInCaseOfErrorsOrWarnings(context); + + // use sysout-over-slf4j to redirect out and err calls to logger + SysOutOverSLF4J.sendSystemOutAndErrToSLF4J(); + } + + /** + * Main method of class. + * + * @param args + * The arguments. + */ + public static void main(String[] args) { + // Start Apache Procrun only if it's a Windows operating system. + if (args.length == 1 && SystemUtils.IS_OS_WINDOWS) { + switch (args[0]) { + case "start": + start(args); + break; + case "stop": + stop(args); + break; + default: + startCMR(); + } + } else { + startCMR(); + } + } + + /** + * Returns the spring bean factory. + * + * @return The spring bean factory. + */ + public static BeanFactory getBeanFactory() { + return beanFactory; + } + + /** + * Getter method for property startedAsService. + * + * @return startedAsService. + */ + public static boolean isStartedAsService() { + return startedAsService; + } +} diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/AbstractObjectSizes.java b/CMR/src/info/novatec/inspectit/cmr/cache/AbstractObjectSizes.java new file mode 100644 index 000000000..f3e848905 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/AbstractObjectSizes.java @@ -0,0 +1,488 @@ +package info.novatec.inspectit.cmr.cache; + +import info.novatec.inspectit.communication.Sizeable; +import info.novatec.inspectit.util.UnderlyingSystemInfo; + +import java.sql.Timestamp; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This is an abstract class that holds general calculations and object sizes that are equal in both + * 32-bit and 64-bit VM. Architecture specific calculations need to be done in implementing classes. + * + * @author Ivan Senic + * + */ +public abstract class AbstractObjectSizes implements IObjectSizes { + + /** + * General sizes of primitive types. + *

+ * Boolean info: Although the Java Virtual Machine defines a boolean type, it only provides very + * limited support for it. There are no Java Virtual Machine instructions solely dedicated to + * operations on boolean values. Instead, expressions in the Java programming language that + * operate on boolean values are compiled to use values of the Java Virtual Machine int data + * type. + */ + public static final long BOOLEAN_SIZE = 1, CHAR_SIZE = 2, SHORT_SIZE = 2, INT_SIZE = 4, FLOAT_SIZE = 4, LONG_SIZE = 8, DOUBLE_SIZE = 8; + + /** + * Default capacity of array list. + */ + private static final int ARRAY_LIST_INITIAL_CAPACITY = 10; + + /** + * Default capacity of {@link HashMap} and {@link ConcurrentHashMap}. + */ + private static final int MAP_INITIAL_CAPACITY = 16; + + /** + * If we need to align between classes to 8 bytes. Only needed when compressed oops are not on + * on 64bit. + */ + private static final boolean ALLIGN_CLASS_CALCULATION = UnderlyingSystemInfo.IS_64BIT && !UnderlyingSystemInfo.IS_COMPRESSED_OOPS; + + /** + * The percentage of size expansion for each object. For security reasons. Default is 20%. + */ + private float objectSecurityExpansionRate = 0.2f; + + /** + * Returns the size of reference in bytes. + * + * @return Size of reference. + */ + public abstract long getReferenceSize(); + + /** + * {@inheritDoc} + */ + @Override + public long getSizeOf(Sizeable sizeable) { + if (null == sizeable) { + return 0; + } + long size = sizeable.getObjectSize(this, ALLIGN_CLASS_CALCULATION); + return ALLIGN_CLASS_CALCULATION ? size : alignTo8Bytes(size); + } + + /** + * {@inheritDoc} + */ + public long getSizeOf(String str) { + if (null == str) { + return 0; + } + long size = this.getSizeOfObjectHeader(); + size += this.getPrimitiveTypesSize(1, 0, 2, 0, 0, 0); + size += this.getSizeOfPrimitiveArray(str.length(), CHAR_SIZE); + return alignTo8Bytes(size); + } + + /** + * {@inheritDoc} + */ + @Override + public long getSizeOf(String... strings) { + Set identityHashCodeSet = new HashSet(); + long size = 0L; + for (String str : strings) { + if (null == str) { + continue; + } + if (identityHashCodeSet.add(System.identityHashCode(str))) { + size += getSizeOf(str); + } + } + return size; + } + + /** + * {@inheritDoc} + */ + public long getSizeOf(Timestamp timestamp) { + if (null == timestamp) { + return 0; + } + // 72 is the number of bytes for instance of GregorianCalendar + // inside Timestamp. However, I can not check if this is null or not. + // In our objects I never found it to be instantiated, so I don't include it. + + long size = this.getSizeOfObjectHeader(); + // java.sql.Timestamp + size += this.getPrimitiveTypesSize(0, 0, 1, 0, 0, 0); + // java.util.Date + size += this.getPrimitiveTypesSize(1, 0, 0, 0, 1, 0); + return alignTo8Bytes(size); + } + + /** + * {@inheritDoc} + */ + public long getSizeOf(List arrayList) { + return this.getSizeOf(arrayList, ARRAY_LIST_INITIAL_CAPACITY); + } + + /** + * {@inheritDoc} + */ + public long getSizeOf(List arrayList, int initialCapacity) { + if (null == arrayList) { + return 0; + } + int capacity = getArrayCapacity(arrayList.size(), initialCapacity); + long size = alignTo8Bytes(this.getSizeOfObjectHeader() + this.getPrimitiveTypesSize(1, 0, 2, 0, 0, 0)); + size += this.getSizeOfArray(capacity); + return alignTo8Bytes(size); + } + + /** + * Returns the capacity of the array list from its size. Note that this calculation will be + * correct only if the array list in initialized with default capacity of + * {@value #ARRAY_LIST_INITIAL_CAPACITY}. + * + * @param size + * Array List size. + * @param initialCapacity + * Initial capacity of Array list. + * @return Capacity of the array that holds elements. + */ + protected int getArrayCapacity(int size, int initialCapacity) { + // from JDK1.7.0_40 the empty list has 0 initial capacity + // capacity goes to initial when first element is added + if (0 == size) { + return 0; + } + + while (initialCapacity < size) { + if (initialCapacity == 0 || initialCapacity == 1) { + initialCapacity = initialCapacity + 1; + } else { + initialCapacity = initialCapacity + (initialCapacity >> 1); + } + } + return initialCapacity; + } + + /** + * {@inheritDoc} + */ + public long getSizeOfHashSet(int hashSetSize) { + return this.getSizeOfHashSet(hashSetSize, MAP_INITIAL_CAPACITY); + } + + /** + * {@inheritDoc} + */ + public long getSizeOfHashSet(int hashSetSize, int initialCapacity) { + long size = this.getSizeOfObjectHeader(); + size += this.getPrimitiveTypesSize(1, 0, 0, 0, 0, 0); + size += this.getSizeOfHashMap(hashSetSize, initialCapacity); + + // One object is used as the value in the map for all entries. This object is shared between + // all HashSet instances, but we have to calculate it for each instance. + if (hashSetSize > 0) { + size += this.getSizeOfObjectObject(); + } + return alignTo8Bytes(size); + } + + /** + * {@inheritDoc} + */ + public long getSizeOfHashMap(int hashMapSize) { + return this.getSizeOfHashMap(hashMapSize, MAP_INITIAL_CAPACITY); + } + + /** + * {@inheritDoc} + */ + public long getSizeOfHashMap(int hashMapSize, int initialCapacity) { + long size = this.getSizeOfObjectHeader(); + size += this.getPrimitiveTypesSize(4, 0, 4, 1, 0, 0); + int mapCapacity = this.getHashMapCapacityFromSize(hashMapSize, initialCapacity); + + // size of the map array for the entries + size += this.getSizeOfArray(mapCapacity); + + // size of the entries + size += hashMapSize * this.getSizeOfHashMapEntry(); + + // To each hash map I add 16 bytes because keySet, entrySet and values fields, that can each + // hold 16 bytes + // These fields are null until these sets are requested by user. + // Thus I add for one + size += getSizeOfHashMapKeyEntrySet(); + + return alignTo8Bytes(size); + } + + /** + * Returns size of HashMap's inner Key or Entry set classes. + * + * @return Returns size of HashMap's inner Key or Entry set classes. + */ + public long getSizeOfHashMapKeyEntrySet() { + // since these are inner classes, one reference to enclosing class is needed + long size = this.getSizeOfObjectHeader() + this.getPrimitiveTypesSize(1, 0, 0, 0, 0, 0); + return alignTo8Bytes(size); + } + + /** + * {@inheritDoc} + */ + public long getSizeOfConcurrentHashMap(int mapSize, int concurrencyLevel) { + long size = this.getSizeOfObjectHeader(); + size += this.getPrimitiveTypesSize(6, 0, 3, 0, 0, 0); + + // array of segments based on capacity + size += this.getSizeOfArray(concurrencyLevel); + + // approximate capacity of each segment + int segmentCapacity = getSegmentCapacityFromSize(mapSize / concurrencyLevel, MAP_INITIAL_CAPACITY / concurrencyLevel); + + // get number of segments + int segments = getNumberOfConcurrentSegments(mapSize, concurrencyLevel); + + // size of each segment based on the capacity, times number of segments + size += segments * this.getSizeOfConcurrentSeqment(segmentCapacity); + + // and for each object in the map there is the reference to the HashEntry in Segment that we + // need to add + // size += mapSize * alignTo8Bytes(this.getReferenceSize()); + size += mapSize * this.getSizeOfHashMapEntry(); + + return alignTo8Bytes(size); + } + + /** + * Returns number of segments in the concurrent hash map. + * + * @param mapSize + * Size of map + * @param concurrencyLevel + * Initial concurrency level. + * @return Number of segments. + */ + protected int getNumberOfConcurrentSegments(int mapSize, int concurrencyLevel) { + // if map is empty there is only one segment created no matter what + return mapSize == 0 ? 1 : concurrencyLevel; + } + + /** + * {@inheritDoc} + */ + public long alignTo8Bytes(long size) { + long d = size % 8; + if (d == 0) { + return size; + } else { + return size + 8 - d; + } + } + + /** + * {@inheritDoc} + */ + @Override + public long getSizeOfObjectObject() { + return alignTo8Bytes(this.getSizeOfObjectHeader()); + } + + /** + * {@inheritDoc} + */ + @Override + public long getSizeOfLongObject() { + long size = this.getSizeOfObjectHeader() + LONG_SIZE; + return alignTo8Bytes(size); + } + + /** + * {@inheritDoc} + */ + @Override + public long getSizeOfIntegerObject() { + long size = this.getSizeOfObjectHeader() + INT_SIZE; + return alignTo8Bytes(size); + } + + /** + * {@inheritDoc} + */ + @Override + public long getSizeOfShortObject() { + long size = this.getSizeOfObjectHeader() + SHORT_SIZE; + return alignTo8Bytes(size); + } + + /** + * {@inheritDoc} + */ + @Override + public long getSizeOfCharacterObject() { + long size = this.getSizeOfObjectHeader() + CHAR_SIZE; + return alignTo8Bytes(size); + } + + /** + * {@inheritDoc} + */ + @Override + public long getSizeOfBooleanObject() { + long size = this.getSizeOfObjectHeader() + BOOLEAN_SIZE; + return alignTo8Bytes(size); + } + + /** + * {@inheritDoc} + */ + public long getPrimitiveTypesSize(int referenceCount, int booleanCount, int intCount, int floatCount, int longCount, int doubleCount) { + // note that the size of the booleans must be aligned to the int size + // thus 1 boolean is in 4 bytes, but are also 2, 3 and 4 booleans in an object packed to int + long booleanSize = 0; + if (booleanCount > 0) { + booleanSize = booleanCount * BOOLEAN_SIZE + INT_SIZE - (booleanCount * BOOLEAN_SIZE) % INT_SIZE; + } + + return booleanSize + referenceCount * getReferenceSize() + intCount * INT_SIZE + floatCount * FLOAT_SIZE + longCount * LONG_SIZE + doubleCount * DOUBLE_SIZE; + } + + /** + * {@inheritDoc} + */ + public float getObjectSecurityExpansionRate() { + return objectSecurityExpansionRate; + } + + /** + * {@inheritDoc} + */ + public void setObjectSecurityExpansionRate(float objectSecurityExpansionRate) { + this.objectSecurityExpansionRate = objectSecurityExpansionRate; + } + + /** + * Calculates the size of the array with out objects in the array - Can only be used on + * non-primitive arrays . + * + * @param arraySize + * Size of array (length). + * @return Size in bytes. + */ + public long getSizeOfArray(int arraySize) { + long size = this.getSizeOfObjectHeader(); + size += this.getPrimitiveTypesSize(arraySize, 0, 1, 0, 0, 0); + return alignTo8Bytes(size); + } + + /** + * Calculates the size of the primitive array with the primitives in the array. + * + * @param arraySize + * Size of array (length). + * @param primitiveSize + * Size in bytes of the primitive type in array + * @return Size in bytes. + */ + public long getSizeOfPrimitiveArray(int arraySize, long primitiveSize) { + long size = this.getSizeOfObjectHeader() + INT_SIZE; + if (ALLIGN_CLASS_CALCULATION) { + size = alignTo8Bytes(size); + } + size += arraySize * primitiveSize; + return alignTo8Bytes(size); + } + + /** + * Calculates the size of the {@link ConcurrentHashMap} segment. + * + * @param seqmentCapacity + * Capacity that segment has. + * + * @return Size in bytes. + */ + protected long getSizeOfConcurrentSeqment(int seqmentCapacity) { + long size = this.getSizeOfObjectHeader(); + size += this.getPrimitiveTypesSize(2, 0, 3, 1, 0, 0); + + // plus the sync in the reentrant lock + size += alignTo8Bytes(this.getSizeOfObjectHeader() + this.getPrimitiveTypesSize(3, 0, 1, 0, 0, 0)); + + // plus just the empty array because we don't know how many objects segment has, this is + // calculated additionally in concurrent map + size += this.getSizeOfArray(seqmentCapacity); + return alignTo8Bytes(size); + } + + /** + * Returns the size of a HashMap entry. Not that the key and value objects are not in this size. + * If HashSet is used the HashMapEntry value object will be a simple Object, thus this size has + * to be added to the HashSet. + * + * @return Returns the size of a HashMap entry. Not that the key and value objects are not in + * this size. If HashSet is used the HashMapEntry value object will be a simple Object, + * thus this size has to be added to the HashSet. + */ + private long getSizeOfHashMapEntry() { + long size = this.getSizeOfObjectHeader(); + size += this.getPrimitiveTypesSize(3, 0, 1, 0, 0, 0); + return alignTo8Bytes(size); + } + + /** + * Returns the capacity of the HashMap from it size. The calculations take the default capacity + * of 16 and default load factor of 0.75. + * + * @param hashMapSize + * Size of hash map. + * @param initialCapacity + * Initial map capacity. + * @return Returns the capacity of the HashMap from it size. The calculations take the default + * capacity of 16 and default load factor of 0.75. + */ + public int getHashMapCapacityFromSize(int hashMapSize, int initialCapacity) { + // from JDK1.7.0_40 the map has 0 initial capacity + // capacity goes to initial when first entry is added + if (hashMapSize == 0) { + return 0; + } + + int capacity = 1; + if (initialCapacity > 0) { + capacity = initialCapacity; + } + float loadFactor = 0.75f; + int threshold = (int) (capacity * loadFactor); + while (threshold < hashMapSize) { + capacity *= 2; + threshold = (int) (capacity * loadFactor); + } + return capacity; + } + + /** + * Returns the concurrent hash map segment capacity from its size and initial capacity. + * + * @param seqmentSize + * Number of elements in the segment. + * @param initialCapacity + * Initial capacity. + * @return Size in bytes. + */ + protected int getSegmentCapacityFromSize(int seqmentSize, int initialCapacity) { + int capacity = initialCapacity; + float loadFactor = 0.75f; + int threshold = (int) (capacity * loadFactor); + while (threshold + 1 <= seqmentSize) { + capacity *= 2; + threshold = (int) (capacity * loadFactor); + } + return capacity; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/AbstractObjectSizesIbm.java b/CMR/src/info/novatec/inspectit/cmr/cache/AbstractObjectSizesIbm.java new file mode 100644 index 000000000..e1313b679 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/AbstractObjectSizesIbm.java @@ -0,0 +1,147 @@ +package info.novatec.inspectit.cmr.cache; + +/** + * This class has some changes in the calculations for the memory size of the objects when IBM JVM + * is run. + * + * @author Ivan Senic + * + */ +public abstract class AbstractObjectSizesIbm extends AbstractObjectSizes { + + /** + * {@inheritDoc} + *

+ * IBM JVM uses 4 bytes per boolean and does not pack booleans together. + */ + @Override + public long getPrimitiveTypesSize(int referenceCount, int booleanCount, int intCount, int floatCount, int longCount, int doubleCount) { + return super.getPrimitiveTypesSize(referenceCount, 0, intCount + booleanCount, floatCount, longCount, doubleCount); + } + + /** + * {@inheritDoc} + *

+ * IBM JVM does not keep size of array in one int field. + */ + @Override + public long getSizeOfArray(int arraySize) { + long size = this.getSizeOfObjectHeader(); + size += this.getPrimitiveTypesSize(arraySize, 0, 0, 0, 0, 0); + return alignTo8Bytes(size); + } + + /** + * {@inheritDoc} + *

+ * IBM JVM does not keep size of array in one int field. + */ + @Override + public long getSizeOfPrimitiveArray(int arraySize, long primitiveSize) { + long size = this.getSizeOfObjectHeader(); + size += arraySize * primitiveSize; + return alignTo8Bytes(size); + } + + /** + * {@inheritDoc} + *

+ * The HashSet in IBM JVM has the shared map value as static variable. + */ + @Override + public long getSizeOfHashSet(int hashSetSize, int initialCapacity) { + long size = this.getSizeOfObjectHeader(); + size += this.getPrimitiveTypesSize(1, 0, 0, 0, 0, 0); + size += this.getSizeOfHashMap(hashSetSize, initialCapacity); + return alignTo8Bytes(size); + } + + /** + * {@inheritDoc} + *

+ * The IBM JVM has the object header of 8 bytes, but the minimum object size is 16 bytes. Don't + * know why this is. + */ + @Override + public long alignTo8Bytes(long size) { + long s = super.alignTo8Bytes(size); + if (size < 16) { + return 16; + } else { + return s; + } + } + + /** + * {@inheritDoc} + *

+ * HashMap from IBM JVM handles in different way the map capacity calculations. + */ + @Override + public int getHashMapCapacityFromSize(int hashMapSize, int initialCapacity) { + return super.getHashMapCapacityFromSize(hashMapSize, calculateHashMapCapacity(initialCapacity)); + } + + /** + * This method is same as the HashMap implementation of IBM JVM calculates the capacity of + * HashMap with given specified capacity from constructors. + * + * @param specifiedCapacity + * Capacity specified in map constructor. + * @return Real starting capacity. + */ + private static int calculateHashMapCapacity(int specifiedCapacity) { + int capacity = 1; + while (capacity < specifiedCapacity) { + capacity <<= 1; + } + return capacity; + } + + /** + * {@inheritDoc} + */ + @Override + protected int getNumberOfConcurrentSegments(int mapSize, int concurrencyLevel) { + int segments = 1; + while (segments < concurrencyLevel) { + segments <<= 1; + } + return segments; + } + + /** + * {@inheritDoc} + *

+ * I can not figure out what I am missing, but seams that I am missing one reference size here. + */ + protected long getSizeOfConcurrentSeqment(int seqmentCapacity) { + long size = super.getSizeOfConcurrentSeqment(seqmentCapacity); + + // for unknown reasons i miss one reference + size += this.getPrimitiveTypesSize(1, 0, 0, 0, 0, 0); + + return alignTo8Bytes(size); + } + + /** + * Returns the concurrent hash map segment capacity from its size and initial capacity. + * + * @param seqmentSize + * Number of elements in the segment. + * @param initialCapacity + * Initial capacity. + * @return Size in bytes. + */ + protected int getSegmentCapacityFromSize(int seqmentSize, int initialCapacity) { + int capacity = initialCapacity; + float loadFactor = 0.75f; + int threshold = (int) (capacity * loadFactor); + while (threshold + 1 < seqmentSize) { + capacity <<= 1; + threshold = (int) (capacity * loadFactor); + } + return capacity; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/IBuffer.java b/CMR/src/info/novatec/inspectit/cmr/cache/IBuffer.java new file mode 100644 index 000000000..74f5c65a3 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/IBuffer.java @@ -0,0 +1,110 @@ +package info.novatec.inspectit.cmr.cache; + +/** + * Interface for Buffer functionality. + * + * @author Ivan Senic + * + * @param + * Type of objects in buffer. + */ +public interface IBuffer { + + /** + * Puts one {@link IBufferElement} in the buffer. + * + * @param element + * Element to be put into the buffer. + */ + void put(IBufferElement element); + + /** + * Performs the eviction from the buffer. The element or elements that needs to be evicted + * depends on buffer implementation. + * + * @throws InterruptedException + * {@link InterruptedException} + */ + void evict() throws InterruptedException; + + /** + * Performs the size analysis of one {@link IBufferElement} in the buffer, that is next in the + * line for analysis. The size of the object is added to the current size of the buffer. + * + * @throws InterruptedException + * {@link InterruptedException} + */ + void analyzeNext() throws InterruptedException; + + /** + * Performs the indexing of one {@link IBufferElement} in the buffer, that is next in the line + * for indexing. + * + * @throws InterruptedException + * {@link InterruptedException} + */ + void indexNext() throws InterruptedException; + + /** + * Empties buffer. + */ + void clearAll(); + + /** + * Returns max size of the buffer. + * + * @return Buffer maximum size in bytes. + */ + long getMaxSize(); + + /** + * Sets max size of the buffer. + * + * @param maxSize + * Maximum size for buffer in bytes. + */ + void setMaxSize(long maxSize); + + /** + * Returns current size of the buffer. + * + * @return Current buffer size in bytes. + */ + long getCurrentSize(); + + /** + * Returns the eviction occupancy percentage, which defines the occupancy percentage of the + * buffer when eviction of the elements should start. + * + * @return Eviction occupancy percentage presented as a float ranging from 0 to 1. + */ + float getEvictionOccupancyPercentage(); + + /** + * Sets the eviction occupancy percentage, which defines the occupancy percentage of the buffer + * when eviction of the elements should start. + * + * @param evictionOccupancyPercentage + * Eviction occupancy percentage presented as a float ranging from 0 to 1. + */ + void setEvictionOccupancyPercentage(float evictionOccupancyPercentage); + + /** + * Returns current occupancy percentage of the buffer. + * + * @return Current buffer occupancy percentage presented as a float ranging from 0 to 1. + */ + float getOccupancyPercentage(); + + /** + * + * @return Returns the oldest element in the buffer. + */ + E getOldestElement(); + + /** + * + * @return Returns the newest element in the buffer. + */ + E getNewestElement(); +} diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/IBufferElement.java b/CMR/src/info/novatec/inspectit/cmr/cache/IBufferElement.java new file mode 100644 index 000000000..e081b005a --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/IBufferElement.java @@ -0,0 +1,122 @@ +package info.novatec.inspectit.cmr.cache; + +/** + * Interface that defines behavior of one element in the buffer. + * + * @author Ivan Senic + * + * @param + * Type of object that buffer element is holding. + */ +public interface IBufferElement { + + /** + * Returns the object hold by the buffer element. + * + * @return Object hold by the element. + */ + E getObject(); + + /** + * Returns the size of buffer element. + * + * @return Size of the buffer element in bytes. + */ + long getBufferElementSize(); + + /** + * Sets the size of buffer element. + * + * @param size + * Size of the buffer element in bytes. + */ + void setBufferElementSize(long size); + + /** + * Calculate the size of the whole buffer element with its object and sets it. + * + * @param objectSizes + * A proper instance of {@link IObjectSizes} that correspond to the JVM. + */ + void calculateAndSetBufferElementSize(IObjectSizes objectSizes); + + /** + * Returns the next buffer element. + * + * @return Next element in respect to the buffer logic where elements are connected or null if + * this element is last inserted element in the buffer. + */ + IBufferElement getNextElement(); + + /** + * Connects this buffer element to the given buffer element. + * + * @param element + * Element that will be logical next element for this buffer element. + */ + void setNextElement(IBufferElement element); + + /** + * Returns if the element has been analyzed. + * + * @return True if analyzed, otherwise no. + */ + boolean isAnalyzed(); + + /** + * Returns if the element has been evicted. + * + * @return True if evicted, otherwise no. + */ + boolean isEvicted(); + + /** + * Returns if the element has been indexed. + * + * @return True if indexed, otherwise no. + */ + boolean isIndexed(); + + /** + * Returns buffer element state, as defined by {@link BufferElementState}. + * + * @return Buffer element state, as defined by {@link BufferElementState}. + */ + BufferElementState getBufferElementState(); + + /** + * + * @param bufferElementState + * Sets buffer element state, as defined by {@link BufferElementState}. + */ + void setBufferElementState(BufferElementState bufferElementState); + + /** + * {@link IBufferElement} state. + * + * @author Ivan Senic + * + */ + public enum BufferElementState { + + /** + * Element is inserted into buffer. + */ + INSERTED, + + /** + * Element is analyzed. + */ + ANALYZED, + + /** + * Element is indexed. + */ + INDEXED, + + /** + * Element is evicted. + */ + EVICTED; + } +} diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/impl/AbstractBufferElementProcessor.java b/CMR/src/info/novatec/inspectit/cmr/cache/impl/AbstractBufferElementProcessor.java new file mode 100644 index 000000000..85a0eafe0 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/impl/AbstractBufferElementProcessor.java @@ -0,0 +1,134 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import info.novatec.inspectit.cmr.cache.IBufferElement; +import info.novatec.inspectit.communication.DefaultData; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; + +/** + * Abstract class for indexing and analyzing processing. + * + * @param + * Type of data to process. + * + * @author Ivan Senic + * + */ +abstract class AbstractBufferElementProcessor { + + /** + * {@link AtomicBuffer} to work on. + */ + protected final AtomicBuffer atomicBuffer; + + /** + * Reference to the last processed element. + */ + protected AtomicReference> lastProcessed; + + /** + * Lock to use during operation. + */ + private Lock lock; + + /** + * Condition to wait on when there s nothing to process. + */ + private Condition condition; + + /** + * Default constructor. + * + * @param atomicBuffer + * {@link AtomicBuffer} to work on. + * @param lastProcessed + * Reference to the last processed element. + * @param lock + * Lock to use during operation. + * @param condition + * Condition to wait on when there s nothing to process. + */ + public AbstractBufferElementProcessor(AtomicBuffer atomicBuffer, AtomicReference> lastProcessed, Lock lock, Condition condition) { + this.atomicBuffer = atomicBuffer; + this.lastProcessed = lastProcessed; + this.lock = lock; + this.condition = condition; + } + + /** + * Processes next element to be processed. Note that this method passes the element to the + * {@link #process(IBufferElement, IBufferElement)} method so that sub-classes can execute the + * real processing. This method handles waiting of element to be available for processing. + * + * @throws InterruptedException + * If {@link InterruptedException} occurs. + */ + public void process() throws InterruptedException { + // wait until there are elements to process + // we wait if: + // 1) queue is empty -> last points to empty element + // 2) all are analyzed -> last to process is not empty element, but points to the empty + // one + while (true) { + IBufferElement lastProcessedElement = lastProcessed.get(); + if (this.atomicBuffer.emptyBufferElement == this.atomicBuffer.last.get() + || (this.atomicBuffer.emptyBufferElement != lastProcessedElement && this.atomicBuffer.emptyBufferElement == lastProcessedElement.getNextElement())) { // NOPMD + lock.lock(); + try { + // check again with lock + lastProcessedElement = this.atomicBuffer.lastAnalyzed.get(); + if (this.atomicBuffer.emptyBufferElement == this.atomicBuffer.last.get() + || (this.atomicBuffer.emptyBufferElement != lastProcessedElement && this.atomicBuffer.emptyBufferElement == lastProcessedElement.getNextElement())) { // NOPMD + condition.await(); + } else { + break; + } + } finally { + lock.unlock(); + } + } else { + break; + } + } + + while (true) { + this.atomicBuffer.clearReadLock.lock(); + try { + IBufferElement elementToProcess = null; + IBufferElement lastProcessElement = lastProcessed.get(); + // if last analyzed points to empty then we take the first added element + if (this.atomicBuffer.emptyBufferElement == lastProcessElement) { // NOPMD + elementToProcess = this.atomicBuffer.last.get(); + } else { + elementToProcess = lastProcessElement.getNextElement(); + } + + // if there is nothing to analyze any more break + if (this.atomicBuffer.emptyBufferElement == elementToProcess) { // NOPMD + break; + } + + if (process(elementToProcess, lastProcessElement)) { + break; + } + } finally { + this.atomicBuffer.clearReadLock.unlock(); + } + } + } + + /** + * Sub-classes should implement this method with the real processing. + * + * @param elementToProcess + * Element to be processed. + * @param lastProcessedElement + * Last successfully processed element. + * @return This method should return true if element was processed and + * false otherwise. + */ + public abstract boolean process(IBufferElement elementToProcess, IBufferElement lastProcessedElement); + +} \ No newline at end of file diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/impl/AnalyzeBufferElementProcessor.java b/CMR/src/info/novatec/inspectit/cmr/cache/impl/AnalyzeBufferElementProcessor.java new file mode 100644 index 000000000..a4d4b8deb --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/impl/AnalyzeBufferElementProcessor.java @@ -0,0 +1,56 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import info.novatec.inspectit.cmr.cache.IBufferElement; +import info.novatec.inspectit.cmr.cache.IBufferElement.BufferElementState; +import info.novatec.inspectit.communication.DefaultData; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; + +/** + * Analyze processor. Performs analyzing of element sizes in the buffer. + * + * @param + * Type of data to process. + * + * @author Ivan Senic + * + */ +class AnalyzeBufferElementProcessor extends AbstractBufferElementProcessor { + + /** + * Default constructor. + * + * @param atomicBuffer + * {@link AtomicBuffer} to work on. + * @param lastProcessed + * Reference to the last processed element. + * @param lock + * Lock to use during operation. + * @param condition + * Condition to wait on when there s nothing to process. + */ + public AnalyzeBufferElementProcessor(AtomicBuffer atomicBuffer, AtomicReference> lastProcessed, Lock lock, Condition condition) { + super(atomicBuffer, lastProcessed, lock, condition); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean process(IBufferElement elementToProcess, IBufferElement lastProcessedElement) { + // only thread that execute compare and set successfully can perform changes + if (lastProcessed.compareAndSet(lastProcessedElement, elementToProcess)) { + // perform analysis + elementToProcess.calculateAndSetBufferElementSize(atomicBuffer.objectSizes); + elementToProcess.setBufferElementState(BufferElementState.ANALYZED); + atomicBuffer.addToCurrentSize(elementToProcess.getBufferElementSize(), true); + atomicBuffer.elementsAnalyzed.incrementAndGet(); + return true; + } + + return false; + } + +} \ No newline at end of file diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/impl/AtomicBuffer.java b/CMR/src/info/novatec/inspectit/cmr/cache/impl/AtomicBuffer.java new file mode 100644 index 000000000..ab43f5c5f --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/impl/AtomicBuffer.java @@ -0,0 +1,733 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import info.novatec.inspectit.cmr.cache.IBuffer; +import info.novatec.inspectit.cmr.cache.IBufferElement; +import info.novatec.inspectit.cmr.cache.IBufferElement.BufferElementState; +import info.novatec.inspectit.cmr.cache.IObjectSizes; +import info.novatec.inspectit.cmr.property.spring.PropertyUpdate; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.indexing.buffer.IBufferTreeComponent; +import info.novatec.inspectit.spring.logger.Log; + +import java.text.NumberFormat; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Buffer uses atomic variables and references to handle the synchronization. Thus, non of its + * methods is synchronized, nor synchronized block were used. However, the whole buffer is thread + * safe. + * + * @author Ivan Senic + * + * @param + * Parameterized type of elements buffer can hold. + */ +@Component +public class AtomicBuffer implements IBuffer { + + /** The logger of this class. */ + @Log + Logger log; + + /** + * Buffer properties. + */ + @Autowired + BufferProperties bufferProperties; + + /** + * Correct interface for calculating object sizes. + */ + @Autowired + IObjectSizes objectSizes; + + /** + * Indexing tree where the elements will be indexed. + */ + @Autowired + IBufferTreeComponent indexingTree; + + /** + * Atomic reference to the first object. + */ + private AtomicReference> first; + + /** + * Max size of the buffer in atomic long. + */ + private AtomicLong maxSize; + + /** + * Eviction occupancy percentage. The value triggers the eviction when the occupancy of the + * buffer is greater than it. Although it is a float value, atomic integer is used via + * {@link Float#intBitsToFloat(int)} and {@link Float#floatToIntBits(float)} methods. + */ + private AtomicInteger evictionOccupancyPercentage; + + /** + * Current size of the buffer in atomic long. + */ + private AtomicLong currentSize = new AtomicLong(); + + /** + * Number of elements added to the buffer. + */ + private AtomicLong elementsAdded = new AtomicLong(); + + /** + * Atomic reference to the last object. + */ + AtomicReference> last; + + /** + * Number of elements evicted from the buffer. + */ + private AtomicLong elementsEvicted = new AtomicLong(); + + /** + * Number of elements that where indexed into indexing tree. + */ + AtomicLong elementsIndexed = new AtomicLong(); + + /** + * Number of elements that where analyzed. + */ + AtomicLong elementsAnalyzed = new AtomicLong(); + + /** + * Atomic reference to the object that was analyzed last. + */ + AtomicReference> lastAnalyzed; + + /** + * Eviction lock. + */ + private ReentrantLock evictLock = new ReentrantLock(); + + /** + * Condition that states that there is nothing to evict currently. + */ + private Condition nothingToEvict = evictLock.newCondition(); + + /** + * Analyze lock. + */ + private ReentrantLock analyzeLock = new ReentrantLock(); + + /** + * Condition that states that there is nothing to analyze currently. + */ + private Condition nothingToAnalyze = analyzeLock.newCondition(); + + /** + * Indexing lock. + */ + private ReentrantLock indexingLock = new ReentrantLock(); + + /** + * Condition that states that there is nothing to index currently. + */ + private Condition nothingToIndex = indexingLock.newCondition(); + + /** + * Atomic reference to the object that was indexed last. + */ + private AtomicReference> lastIndexed; + + /** + * Size of the indexing tree. + */ + AtomicLong indexingTreeSize = new AtomicLong(); + + /** + * Executor service for cleaning the indexing tree. + */ + ExecutorService indexingTreeCleaningExecutorService; + + /** + * Data added to the buffer in bytes. + */ + AtomicLong dataAddedInBytes = new AtomicLong(); + + /** + * Data removed from the buffer in bytes. + */ + AtomicLong dataRemovedInBytes = new AtomicLong(); + + /** + * Marker for empty buffer element. + */ + EmptyBufferElement emptyBufferElement = new EmptyBufferElement(); + + /** + * Amount on bytes flags for tree clean and size update will be set. + */ + volatile long flagsSetOnBytes; + + /** + * This is the read lock that has to be acquired when the size of the buffer or indexing tree is + * updated. + */ + Lock clearReadLock; + + /** + * This is the write lock and has to be acquired when buffer is cleared. This means that during + * the clear of the buffer all operations will be suspended. + */ + private Lock clearWriteLock; + + /** + * {@link AnalyzeBufferElementProcessor} that will analyze the size of the elements. + */ + private AnalyzeBufferElementProcessor analyzeProcessor; + + /** + * {@link IndexBufferElementProcessor} that will index the elements. + */ + private IndexBufferElementProcessor indexProcessor; + + /** + * Default constructor. + */ + public AtomicBuffer() { + ReadWriteLock readWriteCleanLock = new ReentrantReadWriteLock(); + clearReadLock = readWriteCleanLock.readLock(); + clearWriteLock = readWriteCleanLock.writeLock(); + } + + /** + * {@inheritDoc} + *

+ * This method also set the ID of the object that buffer element is holding, thus overwriting + * any earlier set ID. + *

+ * This method is designed for multiply thread access. + */ + public void put(IBufferElement element) { + boolean informAnalyzing = false; + boolean informIndexing = false; + + // the element that is now first has to have a empty buffer element as next one + element.setNextElement(emptyBufferElement); + + while (true) { + // retrieving currently first element + IBufferElement currentlyFirst = first.get(); + + // only thread that successfully execute compare and set will be able to perform changes + if (first.compareAndSet(currentlyFirst, element)) { + + // increment number of added elements + elementsAdded.incrementAndGet(); + + // if currently first is not pointing to marker, it means that we already have + // elements in the buffer, so connect elements + if (!emptyBufferElement.equals(currentlyFirst)) { + currentlyFirst.setNextElement(element); + // see if last index or analyzed points to the last added element + // if so, inform + informAnalyzing = currentlyFirst == lastAnalyzed.get(); + informIndexing = currentlyFirst == lastIndexed.get(); + } else { + // otherwise this is the first element in the buffer, so set last + // and inform both indexing and analyzing + last.set(element); + informAnalyzing = true; + informIndexing = true; + } + + // break from while + break; + } + } + + if (informAnalyzing) { + analyzeLock.lock(); + try { + nothingToAnalyze.signal(); + } finally { + analyzeLock.unlock(); + } + } + + if (informIndexing) { + indexingLock.lock(); + try { + nothingToIndex.signal(); + } finally { + indexingLock.unlock(); + } + } + } + + /** + * {@inheritDoc} + *

+ * The executing thread will wait until the current occupancy percentage of the buffer is + * smaller than eviction occupancy percentage. This method also sets the cleaning flag after + * every {@value #elementsCountForMaintenance}th element evicted. + *

+ * This method is designed for multiply thread access. + */ + public void evict() throws InterruptedException { + // wait until there is need for eviction + while (!shouldEvict()) { + evictLock.lock(); + try { + // check again for avoiding deadlocks + if (!shouldEvict()) { + nothingToEvict.await(); + } + } finally { + evictLock.unlock(); + } + } + + while (true) { + clearReadLock.lock(); + try { + // get the currently last element + IBufferElement currentLastElement = last.get(); + + // check if we really have concrete elements because clear buffer can happen + // anywhere + if (emptyBufferElement.equals(currentLastElement)) { + break; + } + + // set up the values for evicting the fragment of elements + IBufferElement newLastElement = currentLastElement; + long evictionFragmentMaxSize = (long) (this.getMaxSize() * bufferProperties.getEvictionFragmentSizePercentage()); + long fragmentSize = 0; + int elementsInFragment = 0; + + // iterate until size of the eviction fragment is reached + while (fragmentSize < evictionFragmentMaxSize) { + fragmentSize += newLastElement.getBufferElementSize(); + newLastElement.setBufferElementState(BufferElementState.EVICTED); + elementsInFragment++; + newLastElement = newLastElement.getNextElement(); + + // break if we reach the end of queue + if (emptyBufferElement.equals(newLastElement)) { + break; + } + } + + // change the last element to the right one + // only thread that execute compare and set successfully can perform changes + if (last.compareAndSet(currentLastElement, newLastElement)) { + // subtract the fragment size + substractFromCurrentSize(fragmentSize); + + // add evicted elements to the total count + elementsEvicted.addAndGet(elementsInFragment); + + // if the last is now pointing to the empty buffer element, it means that we + // have + // evicted all elements, so first should also point to empty buffer element + // this can only happen in theory + if (emptyBufferElement == last.get()) { + first.set(emptyBufferElement); + } + + // break from while + break; + } + } finally { + clearReadLock.unlock(); + } + } + + } + + /** + * {@inheritDoc} + *

+ * This method is designed for multiply thread access. + */ + public void analyzeNext() throws InterruptedException { + analyzeProcessor.process(); + } + + /** + * {@inheritDoc} + *

+ * This method also performs the cleaning of the indexing tree if the cleaning flag is on. + *

+ * This method is designed for multiply thread access. + */ + public void indexNext() throws InterruptedException { + indexProcessor.process(); + } + + /** + * {@inheritDoc} + */ + public long getMaxSize() { + return maxSize.get(); + } + + /** + * {@inheritDoc} + *

+ * This method is thread safe. + *

+ * Using this method does not provide any check for the supplied new maximum size. Thus, it is + * responsibility of the user to assure that the given value is correct. + */ + public void setMaxSize(long maxSize) { + this.maxSize.set(maxSize); + notifyEvictionIfNeeded(); + } + + /** + * {@inheritDoc} + */ + public long getCurrentSize() { + return currentSize.get(); + } + + /** + * Sets the current size of the buffer. + * + * @param currentSize + * Size in bytes. + */ + public void setCurrentSize(long currentSize) { + this.currentSize.set(currentSize); + notifyEvictionIfNeeded(); + } + + /** + * Adds size value to the current size. + *

+ * This method is thread safe. + * + * @param size + * Size in bytes. + * @param areObjects + * Defines if the size that is added to the current size relates to the objects in + * buffer. True means size is related to objects in buffer, false means that the size + * relates to the indexing tree. + */ + void addToCurrentSize(long size, boolean areObjects) { + currentSize.addAndGet(size); + notifyEvictionIfNeeded(); + if (areObjects) { + dataAddedInBytes.addAndGet(size); + } + } + + /** + * Subtracts size value from the current size. + *

+ * This method is thread safe. + * + * @param size + * Size in bytes. + */ + private void substractFromCurrentSize(long size) { + currentSize.addAndGet(-(size)); + dataRemovedInBytes.addAndGet(size); + } + + /** + * {@inheritDoc} + */ + public float getEvictionOccupancyPercentage() { + return Float.intBitsToFloat(evictionOccupancyPercentage.get()); + } + + /** + * {@inheritDoc} + *

+ * This method is thread safe. + */ + public void setEvictionOccupancyPercentage(float evictionOccupancyPercentage) { + this.evictionOccupancyPercentage.set(Float.floatToIntBits(evictionOccupancyPercentage)); + notifyEvictionIfNeeded(); + } + + /** + * {@inheritDoc} + *

+ * This method is thread safe. + */ + public float getOccupancyPercentage() { + return ((float) currentSize.get()) / maxSize.get(); + } + + /** + * {@inheritDoc} + *

+ * This method is thread safe. + */ + public boolean shouldEvict() { + return getOccupancyPercentage() > Float.intBitsToFloat(evictionOccupancyPercentage.get()); + } + + /** + * {@inheritDoc} + */ + public void clearAll() { + clearWriteLock.lock(); + try { + last.set(emptyBufferElement); + lastAnalyzed.set(emptyBufferElement); + lastIndexed.set(emptyBufferElement); + setCurrentSize(0); + elementsAdded.set(0); + elementsAnalyzed.set(0); + elementsIndexed.set(0); + elementsEvicted.set(0); + indexingTree.clearAll(); + indexingTreeSize.set(0); + dataAddedInBytes.set(0); + dataRemovedInBytes.set(0); + // reference to first has to be reset at the end + first.set(emptyBufferElement); + } finally { + clearWriteLock.unlock(); + } + } + + /** + * Returns the number of inserted elements since the buffer has been created. + * + * @return Number of inserted elements. + */ + public long getInsertedElemenets() { + return elementsAdded.get(); + } + + /** + * Returns the number of evicted elements since the buffer has been created. + * + * @return Number of evicted elements. + */ + public long getEvictedElemenets() { + return elementsEvicted.get(); + } + + /** + * Returns the number of indexed elements since the buffer has been created. + * + * @return Number of indexed elements. + */ + public long getIndexedElements() { + return elementsIndexed.get(); + } + + /** + * Returns the number of analyzed elements since the buffer has been created. + * + * @return Number of analyzed elements. + */ + public long getAnalyzedElements() { + return elementsAnalyzed.get(); + } + + /** + * {@inheritDoc} + */ + public E getOldestElement() { + IBufferElement bufferElement = last.get(); + if (null != bufferElement) { + return bufferElement.getObject(); + } + return null; + } + + /** + * {@inheritDoc} + */ + public E getNewestElement() { + IBufferElement bufferElement = first.get(); + if (null != bufferElement) { + return bufferElement.getObject(); + } + return null; + } + + /** + * Is executed after dependency injection is done to perform any initialization. + * + * @throws Exception + * if an error occurs during {@link PostConstruct} + */ + @PostConstruct + public void postConstruct() throws Exception { + this.maxSize = new AtomicLong(bufferProperties.getInitialBufferSize()); + this.evictionOccupancyPercentage = new AtomicInteger(Float.floatToIntBits(bufferProperties.getEvictionOccupancyPercentage())); + this.objectSizes.setObjectSecurityExpansionRate(bufferProperties.getObjectSecurityExpansionRate(maxSize.get())); + this.first = new AtomicReference>(emptyBufferElement); + this.last = new AtomicReference>(emptyBufferElement); + this.lastAnalyzed = new AtomicReference>(emptyBufferElement); + this.lastIndexed = new AtomicReference>(emptyBufferElement); + this.indexingTreeCleaningExecutorService = Executors.newFixedThreadPool(bufferProperties.getIndexingTreeCleaningThreads()); + this.flagsSetOnBytes = bufferProperties.getFlagsSetOnBytes(this.maxSize.get()); + + // initialize processors + this.analyzeProcessor = new AnalyzeBufferElementProcessor(this, lastAnalyzed, analyzeLock, nothingToAnalyze); + this.indexProcessor = new IndexBufferElementProcessor(this, lastIndexed, indexingLock, nothingToIndex); + + if (log.isInfoEnabled()) { + log.info("|-Using buffer with maximum size " + NumberFormat.getInstance().format(maxSize) + " bytes..."); + log.info("|-Indexing tree maintenance on " + NumberFormat.getInstance().format(flagsSetOnBytes) + " bytes added/removed..."); + log.info("|-Using object expansion rate of " + NumberFormat.getInstance().format(objectSizes.getObjectSecurityExpansionRate() * 100) + "%"); + } + } + + /** + * Updates value of the {@link #evictionOccupancyPercentage}. + */ + @PropertyUpdate(properties = { "buffer.evictionOccupancyPercentage", "buffer.bytesMaintenancePercentage", }) + protected void updateEvictionOccupancyPercentage() { + this.evictionOccupancyPercentage.set(Float.floatToIntBits(bufferProperties.getEvictionOccupancyPercentage())); + } + + /** + * Updates value of the {@link #evictionOccupancyPercentage}. + */ + @PropertyUpdate(properties = { "buffer.bytesMaintenancePercentage", }) + protected void updateBytesMaintenancePercentage() { + this.flagsSetOnBytes = bufferProperties.getFlagsSetOnBytes(this.maxSize.get()); + } + + /** + * Updates the buffer size and to it related properties. + */ + @PropertyUpdate(properties = { "buffer.minOldSpaceOccupancy", "buffer.maxOldSpaceOccupancy", "buffer.minOldSpaceOccupancyActiveTillOldGenSize", "buffer.maxOldSpaceOccupancyActiveFromOldGenSize" }) + protected void updateBufferSizeAndRelated() { + this.maxSize.set(bufferProperties.getInitialBufferSize()); + this.objectSizes.setObjectSecurityExpansionRate(bufferProperties.getObjectSecurityExpansionRate(maxSize.get())); + this.flagsSetOnBytes = bufferProperties.getFlagsSetOnBytes(this.maxSize.get()); + } + + /** + * Updates the object security expansion rate. + */ + @PropertyUpdate(properties = { "buffer.minObjectExpansionRate", "buffer.maxObjectExpansionRate", "buffer.maxObjectExpansionRateActiveTillBufferSize", + "buffer.minObjectExpansionRateActiveFromBufferSize", "buffer.maxObjectExpansionRateActiveFromOccupancy", "buffer.minObjectExpansionRateActiveTillOccupancy" }) + protected void updateObjectSecurityExpansionRate() { + this.objectSizes.setObjectSecurityExpansionRate(bufferProperties.getObjectSecurityExpansionRate(maxSize.get())); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + StringBuffer msg = new StringBuffer(256); + msg.append("The buffer occupancy status: "); + msg.append(NumberFormat.getInstance().format(currentSize.get())); + msg.append(" bytes occupied from total "); + msg.append(NumberFormat.getInstance().format(maxSize.get())); + msg.append(" bytes available ("); + msg.append(NumberFormat.getInstance().format(getOccupancyPercentage() * 100)); + msg.append("%).\nElements processed in the buffer since last clear buffer:\n-Elements added: "); + msg.append(NumberFormat.getInstance().format(elementsAdded.get())); + + msg.append("\n-Elements analyzed: "); + msg.append(NumberFormat.getInstance().format(elementsAnalyzed.get())); + + msg.append("\n-Elements indexed: "); + msg.append(NumberFormat.getInstance().format(elementsIndexed.get())); + + msg.append("\n-Elements evicted: "); + msg.append(NumberFormat.getInstance().format(elementsEvicted.get())); + msg.append('\n'); + return msg.toString(); + } + + /** + * Checks if the eviction should start, and if it does notifies the right thread. + */ + private void notifyEvictionIfNeeded() { + if (shouldEvict()) { + evictLock.lock(); + try { + nothingToEvict.signal(); + } finally { + evictLock.unlock(); + } + } + } + + /** + * Class that serves as a marker for empty buffer element. + * + * @author Ivan Senic + * + */ + private class EmptyBufferElement implements IBufferElement { + + @Override + public E getObject() { + return null; + } + + @Override + public long getBufferElementSize() { + return 0; + } + + @Override + public void setBufferElementSize(long size) { + } + + @Override + public void calculateAndSetBufferElementSize(IObjectSizes objectSizes) { + } + + @Override + public IBufferElement getNextElement() { + return null; + } + + @Override + public void setNextElement(IBufferElement element) { + } + + @Override + public boolean isAnalyzed() { + return false; + } + + @Override + public boolean isEvicted() { + return false; + } + + @Override + public boolean isIndexed() { + return false; + } + + @Override + public info.novatec.inspectit.cmr.cache.IBufferElement.BufferElementState getBufferElementState() { + return null; + } + + @Override + public void setBufferElementState(info.novatec.inspectit.cmr.cache.IBufferElement.BufferElementState bufferElementState) { + } + + } +} diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferAnalyzer.java b/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferAnalyzer.java new file mode 100644 index 000000000..4a5045cbd --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferAnalyzer.java @@ -0,0 +1,47 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import info.novatec.inspectit.cmr.cache.IBuffer; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Thread that invokes the {@link IBuffer#analyzeNext()} method constantly. + * + * @author Ivan Senic + * + */ +@Component +public class BufferAnalyzer extends BufferWorker { + + /** + * Default constructor. Just calls super class constructor. + * + * @param buffer + * Buffer to work on. + */ + @Autowired + public BufferAnalyzer(IBuffer buffer) { + super(buffer, "buffer-analyzing-thread"); + } + + /** + * {@inheritDoc} + */ + @Override + public void work() throws InterruptedException { + getBuffer().analyzeNext(); + } + + /** + * {@inheritDoc} + */ + @Override + @PostConstruct + public synchronized void start() { + super.start(); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferElement.java b/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferElement.java new file mode 100644 index 000000000..0d7557639 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferElement.java @@ -0,0 +1,130 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import info.novatec.inspectit.cmr.cache.IBufferElement; +import info.novatec.inspectit.cmr.cache.IObjectSizes; +import info.novatec.inspectit.communication.DefaultData; + +/** + * Simple implementation of the {@link IBufferElement} interface. + * + * @author Ivan Senic + * + * @param + */ +public class BufferElement implements IBufferElement { + + /** + * Element that is next element in the buffer from the perspective of this buffer element. + */ + private IBufferElement nextElement; + + /** + * Holding object. + */ + private E object; + + /** + * Size of the whole buffer element. + */ + private long bufferElementSize; + + /** + * Buffer element state. + */ + private BufferElementState bufferElementState; + + /** + * Default constructor. + * + * @param object + * Object to hold. + */ + public BufferElement(E object) { + super(); + this.object = object; + this.bufferElementState = BufferElementState.INSERTED; + } + + /** + * {@inheritDoc} + */ + public E getObject() { + return object; + } + + /** + * {@inheritDoc} + */ + public long getBufferElementSize() { + return bufferElementSize; + } + + /** + * {@inheritDoc} + */ + public void setBufferElementSize(long size) { + this.bufferElementSize = size; + } + + /** + * {@inheritDoc} + */ + public IBufferElement getNextElement() { + return nextElement; + } + + /** + * {@inheritDoc} + */ + public void setNextElement(IBufferElement element) { + this.nextElement = element; + } + + /** + * {@inheritDoc} + */ + public void calculateAndSetBufferElementSize(IObjectSizes objectSizes) { + long size = objectSizes.getSizeOfObjectHeader() + objectSizes.getPrimitiveTypesSize(2, 0, 0, 0, 1, 0); + if (null != object) { + size += object.getObjectSize(objectSizes); + } + size += size * objectSizes.getObjectSecurityExpansionRate(); + bufferElementSize = size; + } + + /** + * {@inheritDoc} + */ + public boolean isAnalyzed() { + return bufferElementState.compareTo(BufferElementState.ANALYZED) >= 0; + } + + /** + * {@inheritDoc} + */ + public boolean isEvicted() { + return bufferElementState.compareTo(BufferElementState.EVICTED) >= 0; + } + + /** + * {@inheritDoc} + */ + public boolean isIndexed() { + return bufferElementState.compareTo(BufferElementState.INDEXED) >= 0; + } + + /** + * {@inheritDoc} + */ + public BufferElementState getBufferElementState() { + return bufferElementState; + } + + /** + * {@inheritDoc} + */ + public void setBufferElementState(BufferElementState bufferElementState) { + this.bufferElementState = bufferElementState; + } + +} \ No newline at end of file diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferEvictor.java b/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferEvictor.java new file mode 100644 index 000000000..368fb9b35 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferEvictor.java @@ -0,0 +1,47 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import info.novatec.inspectit.cmr.cache.IBuffer; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Thread that invokes the {@link IBuffer#evict()} method constantly. + * + * @author Ivan Senic + * + */ +@Component +public class BufferEvictor extends BufferWorker { + + /** + * Default constructor. Just calls super class constructor. + * + * @param buffer + * Buffer to work on. + */ + @Autowired + public BufferEvictor(IBuffer buffer) { + super(buffer, "buffer-evicting-thread"); + } + + /** + * {@inheritDoc} + */ + @Override + public void work() throws InterruptedException { + getBuffer().evict(); + } + + /** + * {@inheritDoc} + */ + @Override + @PostConstruct + public synchronized void start() { + super.start(); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferIndexer.java b/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferIndexer.java new file mode 100644 index 000000000..6a25a37f0 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferIndexer.java @@ -0,0 +1,48 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import info.novatec.inspectit.cmr.cache.IBuffer; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Thread that invokes the {@link IBuffer#indexNext()} method constantly. + * + * @author Ivan Senic + * + */ +@Component +public class BufferIndexer extends BufferWorker { + + /** + * Default constructor. Just calls super class constructor. + * + * @param buffer + * Buffer to work on. + */ + @Autowired + public BufferIndexer(IBuffer buffer) { + super(buffer, "buffer-indexing-thread"); + setPriority(NORM_PRIORITY); + } + + /** + * {@inheritDoc} + */ + @Override + public void work() throws InterruptedException { + getBuffer().indexNext(); + } + + /** + * {@inheritDoc} + */ + @Override + @PostConstruct + public synchronized void start() { + super.start(); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferProperties.java b/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferProperties.java new file mode 100644 index 000000000..5148614c3 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferProperties.java @@ -0,0 +1,555 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import info.novatec.inspectit.spring.logger.Log; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryUsage; +import java.lang.management.RuntimeMXBean; +import java.text.NumberFormat; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * Set of properties for one buffer. + * + * @author Ivan Senic + * + */ +@Component +public class BufferProperties { + + /** The logger of this class. */ + @Log + Logger log; + + /** + * Name of the memory pool for old generation. + */ + private static final String OLD_GEN_POOL_NAME = "Old Gen"; + + /** + * Name of the memory pool for tenured generation. Some JVM name like this the old generation + * space. + */ + private static final String TENURED_GEN_POOL_NAME = "Tenured"; + + /** + * Buffer eviction occupancy percentage. + */ + @Value(value = "${buffer.evictionOccupancyPercentage}") + float evictionOccupancyPercentage; + + /** + * Maximum security object expansion rate in percentages. + */ + @Value(value = "${buffer.maxObjectExpansionRate}") + float maxObjectExpansionRate; + + /** + * Minimum security object expansion rate in percentages. + */ + @Value(value = "${buffer.minObjectExpansionRate}") + float minObjectExpansionRate; + + /** + * Maximum security object expansion rate active till this buffer size. + */ + @Value(value = "${buffer.maxObjectExpansionRateActiveTillBufferSize}") + long maxObjectExpansionRateActiveTillBufferSize; + + /** + * Minimum security object expansion rate active from this buffer size. + */ + @Value(value = "${buffer.minObjectExpansionRateActiveFromBufferSize}") + long minObjectExpansionRateActiveFromBufferSize; + + /** + * Buffer occupancy of old gen till which min expansion rate is active. + */ + @Value(value = "${buffer.minObjectExpansionRateActiveTillOccupancy}") + float minObjectExpansionRateActiveTillOccupancy; + + /** + * Buffer occupancy of old gen from which max expansion rate is active. + */ + @Value(value = "${buffer.maxObjectExpansionRateActiveFromOccupancy}") + float maxObjectExpansionRateActiveFromOccupancy; + + /** + * Size of the eviction fragment in percentages, in relation to the max buffer size. + */ + @Value(value = "${buffer.evictionFragmentSizePercentage}") + float evictionFragmentSizePercentage; + + /** + * Number of bytes in % relative to the buffer size that need to be added or removed for the + * buffer so that update and clean of the indexing tree is performed - 5%. + */ + @Value(value = "${buffer.bytesMaintenancePercentage}") + float bytesMaintenancePercentage; + + /** + * Number of threads that are cleaning the indexing tree. + */ + @Value(value = "${buffer.indexingTreeCleaningThreads}") + int indexingTreeCleaningThreads; + + /** + * Time in milliseconds that the indexing thread will wait for the object to be analyzed first. + */ + @Value(value = "${buffer.indexingWaitTime}") + long indexingWaitTime; + + /** + * Size of old space occupancy till which min occupancy will be active. + */ + @Value(value = "${buffer.minOldSpaceOccupancyActiveTillOldGenSize}") + long minOldSpaceOccupancyActiveTillOldGenSize; + + /** + * Size of old space occupancy from which max occupancy will be active. + */ + @Value(value = "${buffer.maxOldSpaceOccupancyActiveFromOldGenSize}") + long maxOldSpaceOccupancyActiveFromOldGenSize; + + /** + * Percentage of the min old generation heap space buffer can occupy. + */ + @Value(value = "${buffer.minOldSpaceOccupancy}") + float minOldSpaceOccupancy; + + /** + * Percentage of the max old generation heap space buffer can occupy. + */ + @Value(value = "${buffer.maxOldSpaceOccupancy}") + float maxOldSpaceOccupancy; + + /** + * Returns buffer eviction occupancy percentage. + * + * @return Buffer eviction occupancy percentage as float. + */ + public float getEvictionOccupancyPercentage() { + return evictionOccupancyPercentage; + } + + /** + * Returns maximum security object expansion rate in percentages. + * + * @return Maximum security object expansion rate in percentages as float. + */ + public float getMaxObjectExpansionRate() { + return maxObjectExpansionRate; + } + + /** + * Returns minimum security object expansion rate in percentages. + * + * @return Minimum security object expansion rate in percentages as float. + */ + public float getMinObjectExpansionRate() { + return minObjectExpansionRate; + } + + /** + * Returns buffer size till which maximum object expansion rate is active. + * + * @return Buffer size in bytes. + */ + public long getMaxObjectExpansionRateActiveTillBufferSize() { + return maxObjectExpansionRateActiveTillBufferSize; + } + + /** + * Returns buffer size from which minimum object expansion rate is active. + * + * @return Buffer size in bytes. + */ + public long getMinObjectExpansionRateActiveFromBufferSize() { + return minObjectExpansionRateActiveFromBufferSize; + } + + /** + * Gets {@link #minObjectExpansionRateActiveTillOccupancy}. + * + * @return {@link #minObjectExpansionRateActiveTillOccupancy} + */ + public float getMinObjectExpansionRateActiveTillOccupancy() { + return minObjectExpansionRateActiveTillOccupancy; + } + + /** + * Gets {@link #maxObjectExpansionRateActiveFromOccupancy}. + * + * @return {@link #maxObjectExpansionRateActiveFromOccupancy} + */ + public float getMaxObjectExpansionRateActiveFromOccupancy() { + return maxObjectExpansionRateActiveFromOccupancy; + } + + /** + * Returns size of the eviction fragment in percentages, in relation to the max buffer size. + * + * @return Eviction fragment in percentages as float. + */ + public float getEvictionFragmentSizePercentage() { + return evictionFragmentSizePercentage; + } + + /** + * Number of bytes that need to be added or removed for the buffer so that update and clean of + * the indexing tree is performed. + * + * @param bufferSize + * Size of the buffer. + * @return Number of bytes that need to be added or removed for the buffer so that update and + * clean of the indexing tree is performed. + */ + public long getFlagsSetOnBytes(long bufferSize) { + return (long) (bytesMaintenancePercentage * bufferSize); + } + + /** + * @return the bytesMaintenancePercentage + */ + public float getBytesMaintenancePercentage() { + return bytesMaintenancePercentage; + } + + /** + * @return Number of indexing tree cleaning threads. + */ + public int getIndexingTreeCleaningThreads() { + return indexingTreeCleaningThreads; + } + + /** + * @return the indexingWaitTime + */ + public long getIndexingWaitTime() { + return indexingWaitTime; + } + + /** + * @return the minOldSpaceOccupancyActiveTillOldGenSize + */ + public long getMinOldSpaceOccupancyActiveTillOldGenSize() { + return minOldSpaceOccupancyActiveTillOldGenSize; + } + + /** + * @return the maxOldSpaceOccupancyActiveFromOldGenSize + */ + public long getMaxOldSpaceOccupancyActiveFromOldGenSize() { + return maxOldSpaceOccupancyActiveFromOldGenSize; + } + + /** + * @return the minOldSpaceOccupancy + */ + public float getMinOldSpaceOccupancy() { + return minOldSpaceOccupancy; + } + + /** + * @return the oldSpaceOccupancy + */ + public float getMaxOldSpaceOccupancy() { + return maxOldSpaceOccupancy; + } + + /** + * Returns the initial buffer size based on the property set. + * + * @return Size in bytes. + */ + public long getInitialBufferSize() { + long bufferSize = 0; + long oldGenMax = getOldGenMax(); + + // If we did not get the value, throw exception + if (oldGenMax == 0) { + throw new RuntimeException("Could not calculate the old generation heap space. Please make sure CMR is running on the provided JVM."); + } + + // Otherwise calculate now + if (oldGenMax > maxOldSpaceOccupancyActiveFromOldGenSize) { + bufferSize = (long) (oldGenMax * maxOldSpaceOccupancy); + } else if (oldGenMax < minOldSpaceOccupancyActiveTillOldGenSize) { + bufferSize = (long) (oldGenMax * minOldSpaceOccupancy); + } else { + // delta is the value that defines how much we can extend the minimum heap + // occupancy + // percentage by analyzing the max memory size + // delta is actually representing additional percentage of heap we can take + // it is always thru that: minHeapSizeOccupancy + delta < + // maxHeapSizeOccupancy + float delta = (maxOldSpaceOccupancy - minOldSpaceOccupancy) + * ((float) (oldGenMax - minOldSpaceOccupancyActiveTillOldGenSize) / (maxOldSpaceOccupancyActiveFromOldGenSize - minOldSpaceOccupancyActiveTillOldGenSize)); + bufferSize = (long) (oldGenMax * (minOldSpaceOccupancy + delta)); + } + return bufferSize; + } + + /** + * Returns memory in bytes for the given argument. + * + * @param argument + * Complete argument value. + * @param memoryToken + * Memory token that is contained in argument. For example 'Xmx' or similar. + * @return Memory value in bytes. + */ + private long getMemorySizeFromArgument(String argument, String memoryToken) { + try { + int index = argument.indexOf(memoryToken) + memoryToken.length(); + + String number = argument.substring(index, argument.length() - 1); + String typeOfMemory = argument.substring(index + number.length()); + + double value = Double.parseDouble(number); + if ("K".equalsIgnoreCase(typeOfMemory)) { + value *= 1024; + } else if ("M".equalsIgnoreCase(typeOfMemory)) { + value *= 1024 * 1024; + } else if ("G".equalsIgnoreCase(typeOfMemory)) { + value *= 1024 * 1024 * 1024; + } else { + value *= 1; + } + + return (long) value; + } catch (Exception e) { + return 0; + } + } + + /** + * Returns object security expansion rate based on the property set and given buffer size. + * + * @param bufferSize + * Buffer's size that expansion rate has to be calculated for. + * @return Expansion rate in percentages. + */ + public float getObjectSecurityExpansionRate(long bufferSize) { + return (getObjectSecurityExpansionRateBufferSize(bufferSize) + getObjectSecurityExpansionRateBufferOccupancy(bufferSize, getOldGenMax())) / 2; + + } + + /** + * Returns object security expansion rate based on the property set and given buffer size. + * + * @param bufferSize + * Buffer's size that expansion rate has to be calculated for. + * @return Expansion rate in percentages. + */ + public float getObjectSecurityExpansionRateBufferSize(long bufferSize) { + if (bufferSize > minObjectExpansionRateActiveFromBufferSize) { + return minObjectExpansionRate; + } else if (bufferSize < maxObjectExpansionRateActiveTillBufferSize) { + return maxObjectExpansionRate; + } else { + // delta is the value that defines how much we can lower the maximum object security + // rate by analyzing the given buffer size + // it is always true that: maxObjectExpansionRate - delta > minObjectExpansionRate + float delta = (maxObjectExpansionRate - minObjectExpansionRate) + * ((float) (bufferSize - maxObjectExpansionRateActiveTillBufferSize) / (minObjectExpansionRateActiveFromBufferSize - maxObjectExpansionRateActiveTillBufferSize)); + return maxObjectExpansionRate - delta; + } + } + + /** + * Returns object security expansion rate based on the property set and given buffer size / old + * generation size. + * + * @param bufferSize + * Buffer's size that expansion rate has to be calculated for. + * @param oldGenMax + * Old generation space. + * @return Expansion rate in percentages. + */ + float getObjectSecurityExpansionRateBufferOccupancy(long bufferSize, long oldGenMax) { + double occupancy = (double) bufferSize / oldGenMax; + + if (occupancy < minObjectExpansionRateActiveTillOccupancy) { + return minObjectExpansionRate; + } else if (occupancy > maxObjectExpansionRateActiveFromOccupancy) { + return maxObjectExpansionRate; + } else { + // delta is the value that defines how much we can lower the maximum object security + // rate by analyzing the given buffer size + // it is always true that: maxObjectExpansionRate - delta > minObjectExpansionRate + float delta = (maxObjectExpansionRate - minObjectExpansionRate) + * ((float) (occupancy - maxObjectExpansionRateActiveFromOccupancy) / (minObjectExpansionRateActiveTillOccupancy - maxObjectExpansionRateActiveFromOccupancy)); + return maxObjectExpansionRate - delta; + } + } + + /** + * Tries to find out the old space generation size. + * + * @return Returns the size of the old generation space in bytes. This method will return + * 0 if the calculation fails. + */ + long getOldGenMax() { + long oldGenMax = 0; + + // try with Memory pool beans + try { + List memBeans = ManagementFactory.getMemoryPoolMXBeans(); + for (MemoryPoolMXBean memBean : memBeans) { + if (memBean.getName().indexOf(OLD_GEN_POOL_NAME) != -1 || memBean.getName().indexOf(TENURED_GEN_POOL_NAME) != -1) { + MemoryUsage memUsage = memBean.getUsage(); + oldGenMax = memUsage.getMax(); + break; + } + } + } catch (Exception e) { + oldGenMax = 0; + } + + // fall back to the Runtime bean for arguments + try { + if (oldGenMax == 0) { + long maxHeap = 0; + long newGen = 0; + RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); + List arguments = runtimeMXBean.getInputArguments(); + for (String arg : arguments) { + if (arg.length() > 4) { + String subedArg = arg.substring(0, 4); + if ("-Xmx".equalsIgnoreCase(subedArg)) { + maxHeap = getMemorySizeFromArgument(arg, subedArg); + } + if ("-Xmn".equalsIgnoreCase(subedArg)) { + newGen = getMemorySizeFromArgument(arg, subedArg); + } + } + } + if (maxHeap != 0 && newGen != 0 && maxHeap > newGen) { + oldGenMax = maxHeap - newGen; + } + } + } catch (Exception e) { + oldGenMax = 0; + } + + return oldGenMax; + } + + /** + * Is executed after dependency injection is done to perform any initialization. + * + * @throws Exception + * if an error occurs during {@link PostConstruct} + */ + @PostConstruct + public void postConstruct() throws Exception { + // log first + if (log.isInfoEnabled()) { + log.info("|-Buffer properties initialized with following values:"); + log.info("||-Eviction occupancy percentage: " + NumberFormat.getInstance().format(evictionOccupancyPercentage * 100) + "%"); + log.info("||-Eviction fragment size percentage: " + NumberFormat.getInstance().format(evictionFragmentSizePercentage * 100) + "%"); + log.info("||-Indexing tree cleaning threads: " + NumberFormat.getInstance().format(indexingTreeCleaningThreads)); + log.info("||-Indexing waiting time: " + NumberFormat.getInstance().format(indexingWaitTime) + " ms"); + log.info("||-Min old generation occupancy percentage active till: " + NumberFormat.getInstance().format(minOldSpaceOccupancyActiveTillOldGenSize) + " bytes"); + log.info("||-Max old generation occupancy percentage active from: " + NumberFormat.getInstance().format(maxOldSpaceOccupancyActiveFromOldGenSize) + " bytes"); + log.info("||-Min old generation occupancy percentage: " + NumberFormat.getInstance().format(minOldSpaceOccupancy * 100) + "%"); + log.info("||-Max old generation occupancy percentage: " + NumberFormat.getInstance().format(maxOldSpaceOccupancy * 100) + "%"); + log.info("||-Max object size expansion: " + NumberFormat.getInstance().format(maxObjectExpansionRate * 100) + "%"); + log.info("||-Min object size expansion: " + NumberFormat.getInstance().format(minObjectExpansionRate * 100) + "%"); + log.info("||-Max object size expansion active from buffer occupancy: " + NumberFormat.getInstance().format(maxObjectExpansionRateActiveFromOccupancy * 100) + "%"); + log.info("||-Min object size expansion active till buffer occupancy: " + NumberFormat.getInstance().format(minObjectExpansionRateActiveTillOccupancy * 100) + " %"); + log.info("||-Max object size expansion active till buffer size: " + NumberFormat.getInstance().format(maxObjectExpansionRateActiveTillBufferSize) + " bytes"); + log.info("||-Min object size expansion active from buffer size: " + NumberFormat.getInstance().format(minObjectExpansionRateActiveFromBufferSize) + " bytes"); + } + + // eviction + if (this.evictionOccupancyPercentage < 0 || this.evictionOccupancyPercentage > 1) { + throw new BeanInitializationException("Buffer properties initialization error: Eviction occupancy must be a percentage value between 0 and 1. Initialization value is: " + + evictionOccupancyPercentage); + } + if (this.evictionFragmentSizePercentage < 0.01 || this.evictionFragmentSizePercentage > 0.5) { + throw new BeanInitializationException("Buffer properties initialization error: Eviction fragment size must be a percentage value between 0.01 and 0.5. Initialization value is: " + + evictionFragmentSizePercentage); + } + + // expansion rate + if (this.minObjectExpansionRateActiveFromBufferSize < this.maxObjectExpansionRateActiveTillBufferSize) { + throw new BeanInitializationException( + "Buffer properties initialization error: Buffer size from which minimum object expansion rate is active can not be lower than buffer size till which maximum object expansion rate is active. Initialization values are: " + + minObjectExpansionRateActiveFromBufferSize + + " (buffer size for min object expansion rate) and " + + maxObjectExpansionRateActiveTillBufferSize + + " (buffer size for max object expansion rate)"); + } + if (this.minObjectExpansionRateActiveTillOccupancy > this.maxObjectExpansionRateActiveFromOccupancy) { + throw new BeanInitializationException( + "Buffer properties initialization error: Buffer occupancy till which minimum object expansion rate is active can not be higher than buffer occupancy from which maximum object expansion rate is active. Initialization values are: " + + minObjectExpansionRateActiveTillOccupancy + + " (buffer occupancy for min object expansion rate) and " + + maxObjectExpansionRateActiveFromOccupancy + + " (buffer occupnacy for max object expansion rate)"); + } + if (this.minObjectExpansionRateActiveTillOccupancy <= 0 || this.minObjectExpansionRateActiveTillOccupancy > 1) { + throw new BeanInitializationException( + "Buffer properties initialization error: The min object expansion rate till buffer old space gen occupancy can not be less or equal than zero, nor greater that one. Initialization value is: " + + this.minObjectExpansionRateActiveTillOccupancy); + } + if (this.maxObjectExpansionRateActiveFromOccupancy <= 0 || this.maxObjectExpansionRateActiveFromOccupancy > 1) { + throw new BeanInitializationException( + "Buffer properties initialization error: The max object expansion rate from buffer old space gen occupancy can not be less or equal than zero, nor greater that one. Initialization value is: " + + this.maxObjectExpansionRateActiveFromOccupancy); + } + + // indexing tree + if (this.getBytesMaintenancePercentage() <= 0 && this.getBytesMaintenancePercentage() > this.getEvictionOccupancyPercentage()) { + throw new BeanInitializationException( + "Buffer properties initialization error: The buffer bytes maintenance percentage that activate the clean and update of the indexing tree can not be less or equal than zero nor bigger that eviction occupancy percentage. Initialization value is: " + + this.getBytesMaintenancePercentage()); + } + if (this.getIndexingTreeCleaningThreads() <= 0) { + throw new BeanInitializationException("Buffer properties initialization error: The number of indexing tree cleaning threads can not be less or equal than zero. Initialization value is: " + + this.getIndexingTreeCleaningThreads()); + } + if (this.indexingWaitTime <= 0) { + throw new BeanInitializationException("Buffer properties initialization error: The indexing wait time can not be less or equal than zero. Initialization value is: " + + this.indexingWaitTime); + } + + // old space settings + if (this.minOldSpaceOccupancyActiveTillOldGenSize <= 0) { + throw new BeanInitializationException( + "Buffer properties initialization error: The min buffer occupancy percentage of the old generation heap space active till old generation size value can not be less or equal than zero. Initialization value is: " + + this.minOldSpaceOccupancyActiveTillOldGenSize); + } + if (this.maxOldSpaceOccupancyActiveFromOldGenSize <= 0) { + throw new BeanInitializationException( + "Buffer properties initialization error: The max buffer occupancy percentage of the old generation heap space active till old generation size value can not be less or equal than zero. Initialization value is: " + + this.maxOldSpaceOccupancyActiveFromOldGenSize); + } + if (this.minOldSpaceOccupancy > this.maxOldSpaceOccupancy) { + throw new BeanInitializationException( + "Buffer properties initialization error: The min buffer occupancy percentage of the old generation heap space can not be higer than max buffer occupancy percentage of the old generation. Initialization values are: " + + this.minOldSpaceOccupancy + "(min), " + this.maxOldSpaceOccupancy + "(max)"); + } + if (this.minOldSpaceOccupancy <= 0 || this.minOldSpaceOccupancy > 1) { + throw new BeanInitializationException( + "Buffer properties initialization error: The min buffer occupancy percentage of the old generation heap space can not be less or equal than zero, nor greater that one. Initialization value is: " + + this.minOldSpaceOccupancy); + } + if (this.maxOldSpaceOccupancy <= 0 || this.maxOldSpaceOccupancy > 1) { + throw new BeanInitializationException( + "Buffer properties initialization error: The max buffer occupancy percentage of the old generation heap space can not be less or equal than zero, nor greater that one. Initialization value is: " + + this.maxOldSpaceOccupancy); + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferWorker.java b/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferWorker.java new file mode 100644 index 000000000..ecf998262 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/impl/BufferWorker.java @@ -0,0 +1,72 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import info.novatec.inspectit.cmr.cache.IBuffer; + +/** + * Abstract class for all threads that work on the buffer. The work each worker performs is defined + * in abstract method {@link #work()}. + * + * @author Ivan Senic + * + */ +public abstract class BufferWorker extends Thread { + + /** + * Buffer to work on. + */ + private IBuffer buffer; + + /** + * Default constructor. Thread is set to be a daemon, to have highest priority and started. + * + * @param buffer + * Buffer to work on. + * @param threadName + * How to name the thread. + */ + public BufferWorker(IBuffer buffer, String threadName) { + this.buffer = buffer; + setName(threadName); + setDaemon(true); + setPriority(MAX_PRIORITY); + } + + /** + * Defines the work to be done on the buffer. + * + * @throws InterruptedException + * {@link InterruptedException} + */ + public abstract void work() throws InterruptedException; + + /** + * Returns buffer that worker is working on. + * + * @return Buffer. + */ + protected IBuffer getBuffer() { + return buffer; + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + while (true) { + // if we go again to the run and we are interrupted we will break; + if (Thread.currentThread().isInterrupted()) { + break; + } + + try { + work(); + } catch (InterruptedException interruptedException) { + // we should never be interrupted, because we don't use this mechanism.. if it + // happens, we preserve evidence that the interruption happened for the code higher + // up, that can figure it out if it wants.. + Thread.currentThread().interrupt(); + } + } + } +} diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/impl/IndexBufferElementProcessor.java b/CMR/src/info/novatec/inspectit/cmr/cache/impl/IndexBufferElementProcessor.java new file mode 100644 index 000000000..3efb8419a --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/impl/IndexBufferElementProcessor.java @@ -0,0 +1,144 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import info.novatec.inspectit.cmr.cache.IBufferElement; +import info.novatec.inspectit.cmr.cache.IBufferElement.BufferElementState; +import info.novatec.inspectit.cmr.util.Converter; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.indexing.impl.IndexingException; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; + +/** + * Index processor. Performs indexing of the elements, update of indexing tree size and cleaning the + * indexing tree. + * + * @param + * Type of data to process. + * + * @author Ivan Senic + * + */ +class IndexBufferElementProcessor extends AbstractBufferElementProcessor { + + /** + * Default constructor. + * + * @param atomicBuffer + * {@link AtomicBuffer} to work on. + * + * @param lastProcessed + * Reference to the last processed element. + * @param lock + * Lock to use during operation. + * @param condition + * Condition to wait on when there s nothing to process. + */ + public IndexBufferElementProcessor(AtomicBuffer atomicBuffer, AtomicReference> lastProcessed, Lock lock, Condition condition) { + super(atomicBuffer, lastProcessed, lock, condition); + } + + /** + * {@inheritDoc} + *

+ * After element processing we check if tree cleaning is needed. + */ + @Override + public void process() throws InterruptedException { + super.process(); + + // if clean flag is set thread should try to perform indexing tree cleaning + // only thread that successfully executes the compare and set will do the cleaning + while (true) { + long dataRemoveInBytesCurrent = atomicBuffer.dataRemovedInBytes.get(); + if (dataRemoveInBytesCurrent > atomicBuffer.flagsSetOnBytes) { + if (atomicBuffer.dataRemovedInBytes.compareAndSet(dataRemoveInBytesCurrent, 0)) { + long time = 0; + if (atomicBuffer.log.isDebugEnabled()) { + time = System.nanoTime(); + } + + atomicBuffer.indexingTree.cleanWithRunnable(atomicBuffer.indexingTreeCleaningExecutorService); + + if (atomicBuffer.log.isDebugEnabled()) { + atomicBuffer.log.debug("Indexing tree cleaning duration: " + Converter.nanoToMilliseconds(System.nanoTime() - time)); + } + break; + } + } else { + break; + } + } + } + + /** + * {@inheritDoc} + *

+ * We wait until element is analyzed to index it. + *

+ * After successful indexing we check if update of indexing tree size is needed and if so update + * it. + * + */ + @Override + public boolean process(IBufferElement elementToProcess, IBufferElement lastProcessedElement) { + // we only index when the element has already been analyzed + if (!elementToProcess.isAnalyzed()) { + try { + Thread.sleep(atomicBuffer.bufferProperties.getIndexingWaitTime()); + } catch (InterruptedException e) { + Thread.interrupted(); + } + // we go back to the while loop, because we want to check if the nextForIndexing + // element has changed + return false; + } + + // only thread that execute compare and set successfully can perform changes + if (lastProcessed.compareAndSet(lastProcessedElement, elementToProcess)) { + try { + // index element + atomicBuffer.indexingTree.put(elementToProcess.getObject()); + elementToProcess.setBufferElementState(BufferElementState.INDEXED); + + // increase number of indexed elements, and perform calculation of the + // indexing tree size if enough elements have been indexed + atomicBuffer.elementsIndexed.incrementAndGet(); + + long dataAddedInBytesCurrent = atomicBuffer.dataAddedInBytes.get(); + if (dataAddedInBytesCurrent > atomicBuffer.flagsSetOnBytes) { + if (atomicBuffer.dataAddedInBytes.compareAndSet(dataAddedInBytesCurrent, 0)) { + long time = 0; + if (atomicBuffer.log.isDebugEnabled()) { + time = System.nanoTime(); + } + while (true) { + // calculation of new size has to be repeated if old size + // compare and set fails + long newSize = atomicBuffer.indexingTree.getComponentSize(atomicBuffer.objectSizes); + newSize += newSize * atomicBuffer.objectSizes.getObjectSecurityExpansionRate(); + long oldSize = atomicBuffer.indexingTreeSize.get(); + if (atomicBuffer.indexingTreeSize.compareAndSet(oldSize, newSize)) { + atomicBuffer.addToCurrentSize(newSize - oldSize, false); + if (atomicBuffer.log.isDebugEnabled()) { + atomicBuffer.log.debug("Indexing tree size update duration: " + Converter.nanoToMilliseconds(System.nanoTime() - time)); + atomicBuffer.log.debug("Indexing tree delta: " + (newSize - oldSize)); + atomicBuffer.log.debug("Indexing tree new size: " + newSize); + } + break; + } + } + } + } + } catch (IndexingException e) { + // indexing exception should not happen + atomicBuffer.log.error(e.getMessage(), e); + } + return true; + } + + return false; + } + +} \ No newline at end of file diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes32Bits.java b/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes32Bits.java new file mode 100644 index 000000000..9ab143dc9 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes32Bits.java @@ -0,0 +1,31 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import info.novatec.inspectit.cmr.cache.AbstractObjectSizes; +import info.novatec.inspectit.cmr.cache.IObjectSizes; + +/** + * This class provides a implementation of {@link IObjectSizes} appropriate for calculations of + * object sizes on 32-bit Sun VM. Works only with Java 7. + * + * @author Ivan Senic + * + */ +public class ObjectSizes32Bits extends AbstractObjectSizes { + + /** + * {@inheritDoc} + */ + @Override + public long getReferenceSize() { + return 4; + } + + /** + * {@inheritDoc} + */ + @Override + public long getSizeOfObjectHeader() { + return 8; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes32BitsIbm.java b/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes32BitsIbm.java new file mode 100644 index 000000000..eb8dd3018 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes32BitsIbm.java @@ -0,0 +1,29 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import info.novatec.inspectit.cmr.cache.AbstractObjectSizesIbm; + +/** + * The object size class for 32bit IBM JVM. Works only with Java 7. + * + * @author Ivan Senic + * + */ +public class ObjectSizes32BitsIbm extends AbstractObjectSizesIbm { + + /** + * {@inheritDoc} + */ + @Override + public long getReferenceSize() { + return 4; + } + + /** + * {@inheritDoc} + */ + @Override + public long getSizeOfObjectHeader() { + return 8; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes64Bits.java b/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes64Bits.java new file mode 100644 index 000000000..aea6c538c --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes64Bits.java @@ -0,0 +1,53 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import info.novatec.inspectit.cmr.cache.AbstractObjectSizes; +import info.novatec.inspectit.cmr.cache.IObjectSizes; + +import java.util.List; + +/** + * This class provides a implementation of {@link IObjectSizes} appropriate for calculations of + * object sizes on 64-bit Sun VM. Works only with Java 7. + * + * @author Ivan Senic + * + */ +public class ObjectSizes64Bits extends AbstractObjectSizes { + + /** + * {@inheritDoc} + */ + @Override + public long getReferenceSize() { + return 8; + } + + /** + * {@inheritDoc} + */ + @Override + public long getSizeOfObjectHeader() { + return 16; + } + + /** + * {@inheritDoc} + *

+ * With no-compressed oops we have internal padding between AbstractList and ArrayList that must + * be counted for. + */ + @Override + public long getSizeOf(List arrayList, int initialCapacity) { + if (null == arrayList) { + return 0; + } + int capacity = getArrayCapacity(arrayList.size(), initialCapacity); + // first AbstractList + long size = alignTo8Bytes(this.getSizeOfObjectHeader() + this.getPrimitiveTypesSize(0, 0, 1, 0, 0, 0)); + + // then ArraList + size = alignTo8Bytes(size + this.getPrimitiveTypesSize(1, 0, 1, 0, 0, 0)); + size += this.getSizeOfArray(capacity); + return alignTo8Bytes(size); + } +} diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes64BitsCompressedOops.java b/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes64BitsCompressedOops.java new file mode 100644 index 000000000..a6f6300f7 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes64BitsCompressedOops.java @@ -0,0 +1,30 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import info.novatec.inspectit.cmr.cache.AbstractObjectSizes; +import info.novatec.inspectit.cmr.cache.IObjectSizes; + +/** + * This class provides a implementation of {@link IObjectSizes} appropriate for calculations of + * object sizes on 64-bit Sun VM when compressed Oops are used. Works only with Java 7. + * + * @author Ivan Senic + * + */ +public class ObjectSizes64BitsCompressedOops extends AbstractObjectSizes { + + /** + * {@inheritDoc} + */ + @Override + public long getReferenceSize() { + return 4; + } + + /** + * {@inheritDoc} + */ + @Override + public long getSizeOfObjectHeader() { + return 12; + } +} diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes64BitsCompressedOopsIbm.java b/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes64BitsCompressedOopsIbm.java new file mode 100644 index 000000000..a5fa62757 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes64BitsCompressedOopsIbm.java @@ -0,0 +1,29 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import info.novatec.inspectit.cmr.cache.AbstractObjectSizesIbm; + +/** + * The object size class for 64bit IBM JVM with compressed Oops. Works only with Java 7. + * + * @author Ivan Senic + * + */ +public class ObjectSizes64BitsCompressedOopsIbm extends AbstractObjectSizesIbm { + + /** + * {@inheritDoc} + */ + @Override + public long getReferenceSize() { + return 4; + } + + /** + * {@inheritDoc} + */ + @Override + public long getSizeOfObjectHeader() { + return 8; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes64BitsIbm.java b/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes64BitsIbm.java new file mode 100644 index 000000000..5d120e53d --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizes64BitsIbm.java @@ -0,0 +1,28 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import info.novatec.inspectit.cmr.cache.AbstractObjectSizesIbm; + +/** + * The object size class for 64bit IBM JVM. Works only with Java 7. + * + * @author Ivan Senic + * + */ +public class ObjectSizes64BitsIbm extends AbstractObjectSizesIbm { + + /** + * {@inheritDoc} + */ + @Override + public long getReferenceSize() { + return 8; + } + + /** + * {@inheritDoc} + */ + public long getSizeOfObjectHeader() { + return 16; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizesFactory.java b/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizesFactory.java new file mode 100644 index 000000000..eb56222cd --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/cache/impl/ObjectSizesFactory.java @@ -0,0 +1,68 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import info.novatec.inspectit.cmr.cache.IObjectSizes; +import info.novatec.inspectit.util.UnderlyingSystemInfo; +import info.novatec.inspectit.util.UnderlyingSystemInfo.JvmProvider; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.stereotype.Component; + +/** + * Factory for returning the correct instance of {@link IObjectSizes} for Spring initialization. The + * factory will check if the IBM JVM is used, and in that case provide the different + * {@link IObjectSizes} objects that support IBM JVM object memory footprint. Further more the + * factory will provide different instances for a 32bit and 64bit JVMs, and even check if the + * compressed OOPs are used with 64bit, and also provide a support for them. + * + * @author Ivan Senic + * + */ +@Component +public class ObjectSizesFactory implements FactoryBean { + + /** + * {@inheritDoc} + */ + @Override + public IObjectSizes getObject() throws Exception { + boolean isIbm = UnderlyingSystemInfo.JVM_PROVIDER == JvmProvider.IBM; + boolean is64Bit = UnderlyingSystemInfo.IS_64BIT; + boolean compresedOops = UnderlyingSystemInfo.IS_COMPRESSED_OOPS; + if (is64Bit && !compresedOops) { + if (isIbm) { + return new ObjectSizes64BitsIbm(); + } else { + return new ObjectSizes64Bits(); + } + } else if (is64Bit && compresedOops) { + if (isIbm) { + return new ObjectSizes64BitsCompressedOopsIbm(); + } else { + return new ObjectSizes64BitsCompressedOops(); + } + } else { + if (isIbm) { + return new ObjectSizes32BitsIbm(); + } else { + return new ObjectSizes32Bits(); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public Class getObjectType() { + return IObjectSizes.class; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isSingleton() { + return true; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/DefaultDataDao.java b/CMR/src/info/novatec/inspectit/cmr/dao/DefaultDataDao.java new file mode 100644 index 000000000..692533e46 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/DefaultDataDao.java @@ -0,0 +1,114 @@ +package info.novatec.inspectit.cmr.dao; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.HttpTimerData; + +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + * All implementing classes of this interface are storing and retrieving the default data objects, + * for example in the database. + * + * @author Patrice Bouillet + * + */ +public interface DefaultDataDao { + + /** + * Persist the {@link DefaultData} object. + * + * @param defaultData + * The object to persist. + */ + void save(DefaultData defaultData); + + /** + * Persists or updates all items in the collection. + * + * @param defaultDataCollection + * The collection with {@link DefaultData} objects to persist or update. + */ + void saveAll(List defaultDataCollection); + + /** + * Returns a list of stored {@link DefaultData} objects in the given interval, starting minus + * the passed timeInterval parameter to the current time. + * + * @param template + * The template object to look for. + * @param timeInterval + * The time interval to look for the objects. Ranging minus the passed time interval + * parameter up until now. + * @return Returns a list of data objects which fulfill the criteria. + */ + List findByExampleWithLastInterval(DefaultData template, long timeInterval); + + /** + * Search for data objects which have an ID greater than in the passed template object. + * + * @param template + * The template object to look for with the ID used as the marker. + * @return Returns a list of data objects which fulfill the criteria. + */ + List findByExampleSinceId(DefaultData template); + + /** + * Search for data objects which have an ID greater than in the passed template object. The + * Method Ident is always ignored. + * + * @param template + * The template object to look for with the ID used as the marker. + * @return Returns a list of data objects which fulfill the criteria. + */ + List findByExampleSinceIdIgnoreMethodId(DefaultData template); + + /** + * Search for data objects which are between the from and to {@link Date} object. + * + * @param template + * The template object to look for. + * @param fromDate + * The start date. + * @param toDate + * The end date. + * @return Returns a list of data objects which fulfill the criteria. + */ + List findByExampleFromToDate(DefaultData template, Date fromDate, Date toDate); + + /** + * Searches for the last saved data object. + * + * @param template + * The template object to look for. + * @return Returns the last saved data object. + */ + DefaultData findByExampleLastData(DefaultData template); + + /** + * Returns the {@link HttpTimerData} list that can be used as the input for the plotting. From + * the template list the platfrom ident will be used as well as all URI and tagged values. + * + * @param templates + * Templates. + * @param fromDate + * From date. + * @param toDate + * To date + * @param retrieveByTag + * If tag values from the templates should be used when retrieving the data. If false + * is passed, URi will be used from templates. + * @return List of {@link HttpTimerData}. + */ + List getChartingHttpTimerDataFromDateToDate(Collection templates, Date fromDate, Date toDate, boolean retrieveByTag); + + /** + * Deletes all default data objects in the database with the given platform ID. + * + * @param platformId + * PLatform id of objects to be deleted. + */ + void deleteAll(Long platformId); + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/ExceptionSensorDataDao.java b/CMR/src/info/novatec/inspectit/cmr/dao/ExceptionSensorDataDao.java new file mode 100644 index 000000000..9bdea5e32 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/ExceptionSensorDataDao.java @@ -0,0 +1,135 @@ +package info.novatec.inspectit.cmr.dao; + +import info.novatec.inspectit.communication.data.AggregatedExceptionSensorData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; + +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +/** + * This layer is used to access the exception sensor information. + * + * @author Eduard Tudenhoefner + * + */ +public interface ExceptionSensorDataDao { + + /** + * Returns a list of {@link ExceptionSensorData} objects. This list can be used to get an + * overview over recorded Exceptions in a target application. + * + * @param template + * The template data object. + * @param limit + * The limit/size of the list. + * @param comparator + * Comparator to compare results with. If null is passed default + * comparator will be used (in this case Timestamp comparator). + * @return List of {@link ExceptionSensorData} objects to get an overview of recorded + * Exceptions. + */ + List getUngroupedExceptionOverview(ExceptionSensorData template, int limit, Comparator comparator); + + /** + * Returns a list of {@link ExceptionSensorData} objects which are between the from and to + * {@link Date} objects. This list can be used to get an overview over recorded Exceptions in a + * target application. + * + * @param template + * The template data object. + * @param limit + * The limit/size of the list. + * @param fromDate + * The start date. + * @param toDate + * The end date. + * @param comparator + * Comparator to compare results with. If null is passed default + * comparator will be used (in this case Timestamp comparator). + * @return List of {@link ExceptionSensorData} objects to get an overview of recorded + * Exceptions. + */ + List getUngroupedExceptionOverview(ExceptionSensorData template, int limit, Date fromDate, Date toDate, Comparator comparator); + + /** + * Returns a list of {@link ExceptionSensorData} objects. This list can be used to get an + * overview over recorded Exceptions in a target application. + * + * @param template + * The template data object. + * @param comparator + * Comparator to compare results with. If null is passed default + * comparator will be used (in this case Timestamp comparator). + * @return List of {@link ExceptionSensorData} objects to get an overview of recorded + * Exceptions. + */ + List getUngroupedExceptionOverview(ExceptionSensorData template, Comparator comparator); + + /** + * Returns a list of {@link ExceptionSensorData} objects which are between the from and to + * {@link Date} objects. This list can be used to get an overview over recorded Exceptions in a + * target application. + * + * @param template + * The template data object. + * @param fromDate + * The start date. + * @param toDate + * The end date. + * @param comparator + * Comparator to compare results with. If null is passed default + * comparator will be used (in this case Timestamp comparator). + * @return List of {@link ExceptionSensorData} objects to get an overview of recorded + * Exceptions. + */ + List getUngroupedExceptionOverview(ExceptionSensorData template, Date fromDate, Date toDate, Comparator comparator); + + /** + * Returns a list of {@link ExceptionSensorData} objects containing all details of a specific + * Exception class. + * + * @param template + * The template data object. + * @return List of {@link ExceptionSensorData} objects containing all details of a specific + * Exception class. + */ + List getExceptionTree(ExceptionSensorData template); + + /** + * Returns a list of {@link AggregatedExceptionSensorData} objects that is used to show an + * overview over Exceptions with specific information about the number of caused event types. + * + * @param template + * The template object to be used for the query. + * @return A list of {@link AggregatedExceptionSensorData} objects with additional information + * about how often a specific eventType was caused. + */ + List getDataForGroupedExceptionOverview(ExceptionSensorData template); + + /** + * Returns a list of {@link AggregatedExceptionSensorData} objects that is used to show an + * overview over Exceptions with specific information about the number of caused event types. + * The returned list contains object that are between the from and to {@link Date} objects. + * + * @param template + * The template object to be used for the query. + * @param fromDate + * The start date. + * @param toDate + * The end date. + * @return A list of {@link AggregatedExceptionSensorData} objects with additional information + * about how often a specific eventType was caused. + */ + List getDataForGroupedExceptionOverview(ExceptionSensorData template, Date fromDate, Date toDate); + + /** + * Returns the exception sensor data list for all error and stack message combinations for the + * throwable type defined in the template. + * + * @param template + * template with throwable type set + * @return {@link ExceptionSensorData} list. + */ + List getStackTraceMessagesForThrowableType(ExceptionSensorData template); +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/HttpTimerDataDao.java b/CMR/src/info/novatec/inspectit/cmr/dao/HttpTimerDataDao.java new file mode 100644 index 000000000..c38767ca5 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/HttpTimerDataDao.java @@ -0,0 +1,70 @@ +package info.novatec.inspectit.cmr.dao; + +import info.novatec.inspectit.communication.data.HttpTimerData; + +import java.util.Date; +import java.util.List; + +/** + * Provides Services to access HttpTimerData information. + * + * @author Stefan Siegl + */ +public interface HttpTimerDataDao { + + /** + * Returns a list of the aggregated timer data for a given template. In this template, only the + * platform id is extracted. + * + * @param httpData + * The template containing the platform id. + * @param includeRequestMethod + * use different request method information for building categorization pairs? + * @return The list of the aggregated timer data object. + */ + List getAggregatedHttpTimerData(HttpTimerData httpData, boolean includeRequestMethod); + + /** + * Returns a list of the aggregated timer data for a given template. In this template, only the + * platform id is extracted. + * + * @param httpData + * The template containing the platform id. + * @param includeRequestMethod + * use different request method information for building categorization pairs? + * @param fromDate + * Date to include data from. + * @param toDate + * Date to include data to. + * @return The list of the aggregated timer data object. + */ + List getAggregatedHttpTimerData(HttpTimerData httpData, boolean includeRequestMethod, Date fromDate, Date toDate); + + /** + * Returns a list of the tagged timer data (aggregated by the value of the inspectit header) for + * a given template. In this template, only the platform id is extracted. + * + * @param httpData + * The template containing the platform id. + * @param includeRequestMethod + * use different request method information for building categorization pairs? + * @return The list of the aggregated timer data object. + */ + List getTaggedAggregatedHttpTimerData(HttpTimerData httpData, boolean includeRequestMethod); + + /** + * Returns a list of the tagged timer data (aggregated by the value of the inspectit header) for + * a given template. In this template, only the platform id is extracted. + * + * @param httpData + * The template containing the platform id. + * @param includeRequestMethod + * use different request method information for building categorization pairs? + * @param fromDate + * Date to include data from. + * @param toDate + * Date to include data to. + * @return The list of the aggregated timer data object. + */ + List getTaggedAggregatedHttpTimerData(HttpTimerData httpData, boolean includeRequestMethod, Date fromDate, Date toDate); +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/InvocationDataDao.java b/CMR/src/info/novatec/inspectit/cmr/dao/InvocationDataDao.java new file mode 100644 index 000000000..f5678855b --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/InvocationDataDao.java @@ -0,0 +1,135 @@ +package info.novatec.inspectit.cmr.dao; + +import info.novatec.inspectit.communication.data.InvocationSequenceData; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +/** + * This layer is used to access the stored invocations. + * + * @author Patrice Bouillet + * + */ +public interface InvocationDataDao { + + /** + * Returns a list of {@link InvocationSequenceData} objects which contain no associations to + * other objects. Thus this list can be used to get an overview of the available invocation + * sequences. The limit defines the size of the list. + * + * @param platformId + * The ID of the platform. + * @param methodId + * The ID of the method. + * @param limit + * The limit/size of the list. + * @param comparator + * Comparator to compare results with. If null is passed default + * comparator will be used (in this case Timestamp comparator). + * + * @return Returns the list of invocation sequences. + */ + List getInvocationSequenceOverview(long platformId, long methodId, int limit, Comparator comparator); + + /** + * Returns a list of {@link InvocationSequenceData} objects which contain no associations to + * other objects. Thus this list can be used to get an overview of the available invocation + * sequences. The limit defines the size of the list. + *

+ * Compared to the above method, this service method returns all invocations for a specific + * agent, not only the invocations for specific methods. + * + * @param platformId + * The ID of the platform. + * @param limit + * The limit/size of the list. + * @param comparator + * Comparator to compare results with. If null is passed default + * comparator will be used (in this case Timestamp comparator). + * + * @return Returns the list of invocation sequences. + */ + List getInvocationSequenceOverview(long platformId, int limit, Comparator comparator); + + /** + * Returns a list of {@link InvocationSequenceData} objects which contain no associations to + * other objects in given time frame. Thus this list can be used to get an overview of the + * available invocation sequences. The limit defines the size of the list. + * + * @param platformId + * The ID of the platform. + * @param methodId + * The ID of the method. + * @param limit + * The limit/size of the list. + * @param fromDate + * Date include invocation from. + * @param toDate + * Date include invocation to. + * @param comparator + * Comparator to compare results with. If null is passed default + * comparator will be used (in this case Timestamp comparator). + * + * @return Returns the list of invocation sequences. + */ + List getInvocationSequenceOverview(long platformId, long methodId, int limit, Date fromDate, Date toDate, Comparator comparator); + + /** + * Returns a list of {@link InvocationSequenceData} objects which contain no associations to + * other objects in given time frame. Thus this list can be used to get an overview of the + * available invocation sequences. The limit defines the size of the list. + *

+ * Compared to the above method, this service method returns all invocations for a specific + * agent, not only the invocations for specific methods. + * + * @param platformId + * The ID of the platform. + * @param limit + * The limit/size of the list. + * @param fromDate + * Date include invocation from. + * @param toDate + * Date include invocation to. + * @param comparator + * Comparator to compare results with. If null is passed default + * comparator will be used (in this case Timestamp comparator). + * + * @return Returns the list of invocation sequences. + */ + List getInvocationSequenceOverview(long platformId, int limit, Date fromDate, Date toDate, Comparator comparator); + + /** + * Returns a list of {@link InvocationSequenceData} objects which contain no associations to + * other objects. Thus this list can be used to get an overview of the available invocation + * sequences. The limit defines the size of the list. + *

+ * Compared with the method above, this service method returns only the invocations which ID is + * in invocation ID collection supplied. + * + * @param platformId + * Platform ID where to look for the objects. If the zero value is passed, looking + * for the object will be done in all platforms. + * @param invocationIdCollection + * Collections of invocations IDs to search. + * @param limit + * The limit/size of the list. + * @param comparator + * Comparator to compare results with. If null is passed default + * comparator will be used (in this case Timestamp comparator). + * @return Returns the list of invocation sequences. + */ + List getInvocationSequenceOverview(long platformId, Collection invocationIdCollection, int limit, Comparator comparator); + + /** + * This service method is used to get all the details of a specific invocation sequence. + * + * @param template + * The template data object. + * @return The detailed invocation sequence object. + */ + InvocationSequenceData getInvocationSequenceDetail(InvocationSequenceData template); + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/MethodIdentDao.java b/CMR/src/info/novatec/inspectit/cmr/dao/MethodIdentDao.java new file mode 100644 index 000000000..a1054ec60 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/MethodIdentDao.java @@ -0,0 +1,83 @@ +package info.novatec.inspectit.cmr.dao; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.model.PlatformIdent; + +import java.util.List; + +import org.springframework.orm.hibernate3.HibernateTemplate; + +/** + * This DAO is used to handle all {@link MethodIdent} objects. + * + * @author Patrice Bouillet + * + */ +public interface MethodIdentDao { + + /** + * Load a specific {@link MethodIdent} from the underlying storage by passing the id. + * + * @param id + * The id of the object. + * @return The found {@link MethodIdent} object. + */ + MethodIdent load(Long id); + + /** + * Execute a findByExample query against the underlying storage. + * + * @param methodIdent + * The {@link MethodIdent} object which serves as the example. + * @return The list of {@link MethodIdent} objects which have the same contents as the passed + * example object. + * @see HibernateTemplate#findByExample(Object) + */ + List findByExample(MethodIdent methodIdent); + + /** + * Saves or updates this {@link MethodIdent} in the underlying storage. + * + * @param methodIdent + * The {@link MethodIdent} object to save or update. + */ + void saveOrUpdate(MethodIdent methodIdent); + + /** + * Deletes this specific {@link MethodIdent} object. + * + * @param methodIdent + * The {@link MethodIdent} object to delete. + */ + void delete(MethodIdent methodIdent); + + /** + * Deletes all {@link MethodIdent} objects which are stored in the passed list. + * + * @param methodIdents + * The list containing the {@link MethodIdent} objects to delete. + */ + void deleteAll(List methodIdents); + + /** + * This method returns a list containing {@link MethodIdent} objects which have an association + * to the given {@link PlatformIdent} object. + * + * @param platformId + * The id of the platform. + * @param methodIdentExample + * The {@link MethodIdent} example object to look for similar object(s). + * @return A list containing the {@link MethodIdent} objects which are already in an association + * with the passed {@link PlatformIdent} object and have identical fields like the + * example object. + */ + List findForPlatformIdent(long platformId, MethodIdent methodIdentExample); + + /** + * Returns all {@link MethodIdent} objects which are saved in the underlying storage. + * + * @return Returns all stored {@link MethodIdent} objects. + */ + List findAll(); + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/MethodIdentToSensorTypeDao.java b/CMR/src/info/novatec/inspectit/cmr/dao/MethodIdentToSensorTypeDao.java new file mode 100644 index 000000000..3e6eae876 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/MethodIdentToSensorTypeDao.java @@ -0,0 +1,42 @@ +package info.novatec.inspectit.cmr.dao; + +import info.novatec.inspectit.cmr.model.MethodIdentToSensorType; + +/** + * This DAO is used to handle all {@link MethodIdentToSensorType} objects. + * + * @author Ivan Senic + * + */ +public interface MethodIdentToSensorTypeDao { + + /** + * Load a specific {@link MethodIdentToSensorType} from the underlying storage by passing the + * id. + * + * @param id + * The id of the object. + * @return The found {@link MethodIdentToSensorType} object. + */ + MethodIdentToSensorType load(Long id); + + /** + * Find the {@link MethodIdentToSensorType} for given method id and method sensor type id. + * + * @param methodId + * Id of the method ident. + * @param methodSensorTypeId + * Id of the method sensor type ident. + * @return Returns {@link MethodIdentToSensorType} object or null if the one does + * not exists for the given method id and methos sensor type id combination. + */ + MethodIdentToSensorType find(long methodId, long methodSensorTypeId); + + /** + * Saves or updates this {@link MethodIdentToSensorType} in the underlying storage. + * + * @param methodIdentToSensorType + * The {@link MethodIdentToSensorType} object to save or update. + */ + void saveOrUpdate(MethodIdentToSensorType methodIdentToSensorType); +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/MethodSensorTypeIdentDao.java b/CMR/src/info/novatec/inspectit/cmr/dao/MethodSensorTypeIdentDao.java new file mode 100644 index 000000000..065425166 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/MethodSensorTypeIdentDao.java @@ -0,0 +1,70 @@ +package info.novatec.inspectit.cmr.dao; + +import info.novatec.inspectit.cmr.model.MethodSensorTypeIdent; + +import java.util.List; + +import org.springframework.orm.hibernate3.HibernateTemplate; + +/** + * This DAO is used to handle all {@link MethodSensorTypeIdent} objects. + * + * @author Patrice Bouillet + * + */ +public interface MethodSensorTypeIdentDao { + + /** + * Load a specific {@link MethodSensorTypeIdent} from the underlying storage by passing the id. + * + * @param id + * The id of the object. + * @return The found {@link MethodSensorTypeIdent} object. + */ + MethodSensorTypeIdent load(Long id); + + /** + * Execute a findByExample query against the underlying storage. + * + * @param platformId + * Platform ID sensor should belong to. + * @param methodSensorTypeIdent + * The {@link MethodSensorTypeIdent} object which serves as the example. + * @return The list of {@link MethodSensorTypeIdent} objects which have the same contents as the + * passed example object. + * @see HibernateTemplate#findByExample(Object) + */ + List findByExample(long platformId, MethodSensorTypeIdent methodSensorTypeIdent); + + /** + * Saves or updates this {@link MethodSensorTypeIdent} in the underlying storage. + * + * @param methodSensorTypeIdent + * The {@link MethodSensorTypeIdent} object to save or update. + */ + void saveOrUpdate(MethodSensorTypeIdent methodSensorTypeIdent); + + /** + * Deletes this specific {@link MethodSensorTypeIdent} object. + * + * @param methodSensorTypeIdent + * The {@link MethodSensorTypeIdent} object to delete. + */ + void delete(MethodSensorTypeIdent methodSensorTypeIdent); + + /** + * Deletes all {@link MethodSensorTypeIdent} objects which are stored in the passed list. + * + * @param methodSensorTypeIdents + * The list containing the {@link MethodSensorTypeIdent} objects to delete. + */ + void deleteAll(List methodSensorTypeIdents); + + /** + * Returns all {@link MethodSensorTypeIdent} objects which are saved in the underlying storage. + * + * @return Returns all stored {@link MethodSensorTypeIdent} objects. + */ + List findAll(); + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/PlatformIdentDao.java b/CMR/src/info/novatec/inspectit/cmr/dao/PlatformIdentDao.java new file mode 100644 index 000000000..7ea2d2b25 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/PlatformIdentDao.java @@ -0,0 +1,97 @@ +package info.novatec.inspectit.cmr.dao; + +import info.novatec.inspectit.cmr.model.PlatformIdent; + +import java.util.List; + +import org.springframework.orm.hibernate3.HibernateTemplate; + +/** + * This DAO is used to handle all {@link PlatformIdent} objects. + * + * @author Patrice Bouillet + * + */ +public interface PlatformIdentDao { + + /** + * Load a specific {@link PlatformIdent} from the underlying storage by passing the id. + * + * @param id + * The id of the object. + * @return The found {@link PlatformIdent} object. + */ + PlatformIdent load(Long id); + + /** + * Execute a findByExample query against the underlying storage. + * + * @param platformIdent + * The {@link PlatformIdent} object which serves as the example. + * @return The list of {@link PlatformIdent} objects which have the same contents as the passed + * example object. + * @see HibernateTemplate#findByExample(Object) + */ + List findByExample(PlatformIdent platformIdent); + + /** + * Saves or updates this {@link PlatformIdent} in the underlying storage. + * + * @param platformIdent + * The {@link PlatformIdent} object to save or update. + */ + void saveOrUpdate(PlatformIdent platformIdent); + + /** + * Deletes this specific {@link PlatformIdent} object. + * + * @param platformIdent + * The {@link PlatformIdent} object to delete. + */ + void delete(PlatformIdent platformIdent); + + /** + * Deletes all {@link PlatformIdent} objects which are stored in the passed list. + * + * @param platformIdents + * The list containing the {@link PlatformIdent} objects to delete. + */ + void deleteAll(List platformIdents); + + /** + * Returns all {@link PlatformIdent} objects which are saved in the underlying storage. + *

+ * Object will be sorted by agent name. + * + * @return Returns all stored {@link PlatformIdent} objects. + */ + List findAll(); + + /** + * Evicts the passed {@link PlatformIdent} object from the session. + * + * @param platformIdent + * The {@link PlatformIdent} object to evict from the session. + */ + void evict(PlatformIdent platformIdent); + + /** + * Evicts all {@link PlatformIdent} objects from the session. + * + * @param platformIdents + * The list of {@link PlatformIdent} objects to evict from the session. + */ + void evictAll(List platformIdents); + + /** + * Executes the same query as {@link #findAll()} but initialized the lazy collections + * afterwards. Only for one agent. + * + * @param id + * Id of wanted agent. + * + * @return Returns one {@link PlatformIdent} object and initializes the collections. + */ + PlatformIdent findInitialized(long id); + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/PlatformSensorTypeIdentDao.java b/CMR/src/info/novatec/inspectit/cmr/dao/PlatformSensorTypeIdentDao.java new file mode 100644 index 000000000..1d1a54324 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/PlatformSensorTypeIdentDao.java @@ -0,0 +1,72 @@ +package info.novatec.inspectit.cmr.dao; + +import info.novatec.inspectit.cmr.model.PlatformSensorTypeIdent; + +import java.util.List; + +import org.springframework.orm.hibernate3.HibernateTemplate; + +/** + * This DAO is used to handle all {@link PlatformSensorTypeIdent} objects. + * + * @author Patrice Bouillet + * + */ +public interface PlatformSensorTypeIdentDao { + + /** + * Load a specific {@link PlatformSensorTypeIdent} from the underlying storage by passing the + * id. + * + * @param id + * The id of the object. + * @return The found {@link PlatformSensorTypeIdent} object. + */ + PlatformSensorTypeIdent load(Long id); + + /** + * Execute a findByExample query against the underlying storage. + * + * @param platformId + * Platform ID sensor should be bound to. + * @param platformSensorTypeIdent + * The {@link PlatformSensorTypeIdent} object which serves as the example. + * @return The list of {@link PlatformSensorTypeIdent} objects which have the same contents as + * the passed example object. + * @see HibernateTemplate#findByExample(Object) + */ + List findByExample(long platformId, PlatformSensorTypeIdent platformSensorTypeIdent); + + /** + * Saves or updates this {@link PlatformSensorTypeIdent} in the underlying storage. + * + * @param platformSensorTypeIdent + * The {@link PlatformSensorTypeIdent} object to save or update. + */ + void saveOrUpdate(PlatformSensorTypeIdent platformSensorTypeIdent); + + /** + * Deletes this specific {@link PlatformSensorTypeIdent} object. + * + * @param platformSensorTypeIdent + * The {@link PlatformSensorTypeIdent} object to delete. + */ + void delete(PlatformSensorTypeIdent platformSensorTypeIdent); + + /** + * Deletes all {@link PlatformSensorTypeIdent} objects which are stored in the passed list. + * + * @param platformSensorTypeIdents + * The list containing the {@link PlatformSensorTypeIdent} objects to delete. + */ + void deleteAll(List platformSensorTypeIdents); + + /** + * Returns all {@link PlatformSensorTypeIdent} objects which are saved in the underlying + * storage. + * + * @return Returns all stored {@link PlatformSensorTypeIdent} objects. + */ + List findAll(); + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/SqlDataDao.java b/CMR/src/info/novatec/inspectit/cmr/dao/SqlDataDao.java new file mode 100644 index 000000000..6d49ea0fe --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/SqlDataDao.java @@ -0,0 +1,66 @@ +package info.novatec.inspectit.cmr.dao; + +import info.novatec.inspectit.communication.data.SqlStatementData; + +import java.util.Date; +import java.util.List; + +/** + * @author Patrice Bouillet + * + */ +public interface SqlDataDao { + + /** + * Returns a list of the SQL statements for a given template. In the template, only the platform + * id is extracted. If the template holds the SQL query string, only objects with this query + * string will be returned. + * + * @param sqlStatementData + * The template containing the platform id. + * @return The list of the SQL statements. + */ + List getAggregatedSqlStatements(SqlStatementData sqlStatementData); + + /** + * Returns a list of the SQL statements for a given template in a time frame. In the template, + * only the platform id is extracted. If the template holds the SQL query string, only objects + * with this query string will be returned. + * + * @param sqlStatementData + * The template containing the platform id. + * @param fromDate + * Date to include data from. + * @param toDate + * Date to include data to. + * @return The list of the SQL statements. + */ + List getAggregatedSqlStatements(SqlStatementData sqlStatementData, Date fromDate, Date toDate); + + /** + * Returns a list of the SQL statements for a given template aggregated by the parameters. In + * the template, only the platform id is extracted. If the template holds the SQL query string, + * only objects with this query string will be returned. + * + * @param sqlStatementData + * The template containing the platform id. + * @return The list of the SQL statements. + */ + List getParameterAggregatedSqlStatements(SqlStatementData sqlStatementData); + + /** + * Returns a list of the SQL statements for a given template in a time frame aggregated by the + * parameters. In the template, only the platform id is extracted. If the template holds the SQL + * query string, only objects with this query string will be returned. + * + * @param sqlStatementData + * The template containing the platform id. + * @param fromDate + * Date to include data from. + * @param toDate + * Date to include data to. + * @return The list of the SQL statements. + */ + List getParameterAggregatedSqlStatements(SqlStatementData sqlStatementData, Date fromDate, Date toDate); + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/StorageDataDao.java b/CMR/src/info/novatec/inspectit/cmr/dao/StorageDataDao.java new file mode 100644 index 000000000..65ba0bcc2 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/StorageDataDao.java @@ -0,0 +1,137 @@ +package info.novatec.inspectit.cmr.dao; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.SystemInformationData; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.label.type.AbstractStorageLabelType; + +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + * Storage data dao interface. + * + * @author Ivan Senic + * + */ +public interface StorageDataDao { + + /** + * Saves a label to the database if the same label does not exists. + * + * @param label + * Label to save. + * @return True if label was saved, false otherwise. + */ + boolean saveLabel(AbstractStorageLabel label); + + /** + * Removes a label. + * + * @param label + * Label to remove. + */ + void removeLabel(AbstractStorageLabel label); + + /** + * Removes a collection of labels. + * + * @param labels + * Labels. + */ + void removeLabels(Collection> labels); + + /** + * Returns all labels registered on the CMR. + * + * @return Returns all labels registered on the CMR. + */ + List> getAllLabels(); + + /** + * Returns all labels of specified type registered on the CMR. + * + * @param + * Type of label. + * @param labelType + * Label type. + * @return Returns all labels of specified type registered on the CMR. + */ + List> getAllLabelsForType(AbstractStorageLabelType labelType); + + /** + * Saves the {@link AbstractStorageLabelType} to the database. The label will be saved only if + * the {@link AbstractStorageLabelType#isMultiType()} is true or no instances of the label type + * are already saved. + * + * @param labelType + * Label type to save. + */ + void saveLabelType(AbstractStorageLabelType labelType); + + /** + * Removes label type from database. + * + * @param labelType + * Label type to remove. + * @throws Exception + * If there are still labels of this type existing in the database. + */ + void removeLabelType(AbstractStorageLabelType labelType) throws Exception; + + /** + * Returns all instances of desired label type. + * + * @param + * Label value type. + * @param labelTypeClass + * Label type class. + * @return List of all instances. + */ + > List getLabelTypes(Class labelTypeClass); + + /** + * Returns all label types. + * + * @return Returns all label types. + */ + List> getAllLabelTypes(); + + /** + * Returns all the data that is indexed in the indexing tree for a specific platform ident. Not + * that is possible that some data is contained two times in the return list, ones as a object + * in the list, ones as a part of invocation that is in the list. + * + * @param platformId + * Id of agent. + * @param fromDate + * Date to search data from. Can be null for no restriction. + * @param toDate + * Date to search data to. Can be null for no restriction. + * @return List of {@link DefaultData} objects. + */ + List getAllDefaultDataForAgent(long platformId, Date fromDate, Date toDate); + + /** + * Returns the fresh data from the buffer which IDs correspond to the given IDs. + * + * @param elementIds + * Id to search for. + * @param platformIdent + * PLatform ident that elements belong to. Value 0 will ignore the platform ident and + * search the complete buffer. + * @return Data to be store in storage. + */ + List getDataFromIdList(Collection elementIds, long platformIdent); + + /** + * Returns the last {@link SystemInformationData} for every agent provided in the list. + * + * @param agentIds + * Collection of agent IDs. + * @return List of {@link SystemInformationData}. + */ + List getSystemInformationData(Collection agentIds); + +} \ No newline at end of file diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/TimerDataDao.java b/CMR/src/info/novatec/inspectit/cmr/dao/TimerDataDao.java new file mode 100644 index 000000000..0e6343b38 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/TimerDataDao.java @@ -0,0 +1,39 @@ +package info.novatec.inspectit.cmr.dao; + +import info.novatec.inspectit.communication.data.TimerData; + +import java.util.Date; +import java.util.List; + +/** + * The DAO for timer data objects. + * + * @author Ivan Senic + * + */ +public interface TimerDataDao { + + /** + * Returns a list of the aggregated timer data for a given template. In this template, only the + * platform id is extracted. + * + * @param timerData + * The template containing the platform id. + * @return The list of the aggregated timer data object. + */ + List getAggregatedTimerData(TimerData timerData); + + /** + * Returns a list of the timer data for a given template for a time frame. In this template, + * only the platform id is extracted. + * + * @param timerData + * The template containing the platform id. + * @param fromDate + * Date to include data from. + * @param toDate + * Date to include data to. + * @return The list of the timer data object. + */ + List getAggregatedTimerData(TimerData timerData, Date fromDate, Date toDate); +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/impl/AbstractBufferDataDao.java b/CMR/src/info/novatec/inspectit/cmr/dao/impl/AbstractBufferDataDao.java new file mode 100644 index 000000000..751eb82d9 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/impl/AbstractBufferDataDao.java @@ -0,0 +1,179 @@ +package info.novatec.inspectit.cmr.dao.impl; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.indexing.IIndexQuery; +import info.novatec.inspectit.indexing.aggregation.IAggregator; +import info.novatec.inspectit.indexing.aggregation.impl.AggregationPerformer; +import info.novatec.inspectit.indexing.buffer.IBufferTreeComponent; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Abstract class for all buffer data DAO service. + * + * @param + * Type of the data to be queried. + * + * @author Ivan Senic + * + */ +public abstract class AbstractBufferDataDao { + + /** + * Indexing tree to search for data. + */ + @Autowired + private IBufferTreeComponent indexingTree; + + /** + * Executes the query on the indexing tree. + * + * @param indexQuery + * Index query to execute. + * + * @return Result list. + */ + protected List executeQuery(IIndexQuery indexQuery) { + return this.executeQuery(indexQuery, null, null, -1); + } + + /** + * Executes the query on the indexing tree. If the {@link IAggregator} is not null + * then the results will be aggregated based on the given {@link IAggregator}. + * + * @param indexQuery + * Index query to execute. + * @param aggregator + * {@link IAggregator}. Pass null if no aggregation is needed. + * @return Result list. + */ + protected List executeQuery(IIndexQuery indexQuery, IAggregator aggregator) { + return this.executeQuery(indexQuery, aggregator, null, -1); + } + + /** + * Executes the query on the indexing tree. Results can be sorted by comparator. + * + * @param indexQuery + * Index query to execute. + * @param comparator + * If supplied the final result list will be sorted by this comparator. + * @return Result list. + */ + protected List executeQuery(IIndexQuery indexQuery, Comparator comparator) { + return this.executeQuery(indexQuery, null, comparator, -1); + } + + /** + * Executes the query on the indexing tree. Furthermore the result list can be limited. + * + * @param indexQuery + * Index query to execute. + * @param limit + * Limit the number of results by given number. Value -1 means no limit. + * @return Result list. + */ + protected List executeQuery(IIndexQuery indexQuery, int limit) { + return this.executeQuery(indexQuery, null, null, limit); + } + + /** + * Executes the query on the indexing tree. If the {@link IAggregator} is not null + * then the results will be aggregated based on the given {@link IAggregator}. + * + * @param indexQuery + * Index query to execute. + * @param aggregator + * {@link IAggregator}. Pass null if no aggregation is needed. + * @param comparator + * If supplied the final result list will be sorted by this comparator. + * @return Result list. + */ + protected List executeQuery(IIndexQuery indexQuery, IAggregator aggregator, Comparator comparator) { + return this.executeQuery(indexQuery, aggregator, comparator, -1); + } + + /** + * Executes the query on the indexing tree. If the {@link IAggregator} is not null + * then the results will be aggregated based on the given {@link IAggregator}. Furthermore the + * result list can be limited. + * + * @param indexQuery + * Index query to execute. + * @param aggregator + * {@link IAggregator}. Pass null if no aggregation is needed. + * @param limit + * Limit the number of results by given number. Value -1 means no limit. + * @return Result list. + */ + protected List executeQuery(IIndexQuery indexQuery, IAggregator aggregator, int limit) { + return this.executeQuery(indexQuery, aggregator, null, limit); + } + + /** + * Executes the query on the indexing tree. Results can be sorted by comparator. Furthermore the + * result list can be limited. + * + * @param indexQuery + * Index query to execute. + * + * @param comparator + * If supplied the final result list will be sorted by this comparator. + * @param limit + * Limit the number of results by given number. Value -1 means no limit. + * @return Result list. + */ + protected List executeQuery(IIndexQuery indexQuery, Comparator comparator, int limit) { + return this.executeQuery(indexQuery, null, comparator, limit); + } + + /** + * Executes the query on the indexing tree. If the {@link IAggregator} is not null + * then the results will be aggregated based on the given {@link IAggregator}. Results can be + * sorted by comparator. Furthermore the result list can be limited. + * + * @param indexQuery + * Index query to execute. + * @param aggregator + * {@link IAggregator}. Pass null if no aggregation is needed. + * @param comparator + * If supplied the final result list will be sorted by this comparator. + * @param limit + * Limit the number of results by given number. Value -1 means no limit. + * @return Result list. + */ + protected List executeQuery(IIndexQuery indexQuery, IAggregator aggregator, Comparator comparator, int limit) { + List data = indexingTree.query(indexQuery); + + if (null != aggregator) { + AggregationPerformer aggregationPerformer = new AggregationPerformer(aggregator); + aggregationPerformer.processCollection(data); + data = aggregationPerformer.getResultList(); + } + + if (null != comparator) { + Collections.sort(data, comparator); + } + + if (limit > -1 && data.size() > limit) { + data = new ArrayList(data.subList(0, limit)); + } + + return data; + } + + /** + * Gets {@link #indexingTree}. + * + * @return {@link #indexingTree} + */ + protected IBufferTreeComponent getIndexingTree() { + return indexingTree; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferExceptionSensorDataDaoImpl.java b/CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferExceptionSensorDataDaoImpl.java new file mode 100644 index 000000000..d629e9916 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferExceptionSensorDataDaoImpl.java @@ -0,0 +1,109 @@ +package info.novatec.inspectit.cmr.dao.impl; + +import info.novatec.inspectit.cmr.dao.ExceptionSensorDataDao; +import info.novatec.inspectit.communication.comparator.DefaultDataComparatorEnum; +import info.novatec.inspectit.communication.data.AggregatedExceptionSensorData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.indexing.IIndexQuery; +import info.novatec.inspectit.indexing.aggregation.Aggregators; +import info.novatec.inspectit.indexing.query.factory.impl.ExceptionSensorDataQueryFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +/** + * {@link ExceptionSensorDataDao} that works with the data from the buffer. + * + * @author Ivan Senic + * + */ +@Repository +public class BufferExceptionSensorDataDaoImpl extends AbstractBufferDataDao implements ExceptionSensorDataDao { + + /** + * Index query provider. + */ + @Autowired + private ExceptionSensorDataQueryFactory exceptionSensorDataQueryFactory; + + /** + * {@inheritDoc} + */ + public List getUngroupedExceptionOverview(ExceptionSensorData template, int limit, Comparator comparator) { + return this.getUngroupedExceptionOverview(template, limit, null, null, comparator); + } + + /** + * {@inheritDoc} + */ + public List getUngroupedExceptionOverview(ExceptionSensorData template, int limit, Date fromDate, Date toDate, Comparator comparator) { + IIndexQuery query = exceptionSensorDataQueryFactory.getUngroupedExceptionOverviewQuery(template, limit, fromDate, toDate); + if (null != comparator) { + return super.executeQuery(query, comparator, limit); + } else { + return super.executeQuery(query, DefaultDataComparatorEnum.TIMESTAMP, limit); + } + } + + /** + * {@inheritDoc} + */ + public List getUngroupedExceptionOverview(ExceptionSensorData template, Comparator comparator) { + return this.getUngroupedExceptionOverview(template, -1, null, null, comparator); + } + + /** + * {@inheritDoc} + */ + public List getUngroupedExceptionOverview(ExceptionSensorData template, Date fromDate, Date toDate, Comparator comparator) { + return this.getUngroupedExceptionOverview(template, -1, fromDate, toDate, comparator); + } + + /** + * {@inheritDoc} + */ + public List getExceptionTree(ExceptionSensorData template) { + IIndexQuery query = exceptionSensorDataQueryFactory.getExceptionTreeQuery(template); + List results = super.executeQuery(query); + Collections.reverse(results); + return results; + } + + /** + * {@inheritDoc} + */ + public List getDataForGroupedExceptionOverview(ExceptionSensorData template) { + return this.getDataForGroupedExceptionOverview(template, null, null); + } + + /** + * {@inheritDoc} + */ + public List getDataForGroupedExceptionOverview(ExceptionSensorData template, Date fromDate, Date toDate) { + IIndexQuery query = exceptionSensorDataQueryFactory.getDataForGroupedExceptionOverviewQuery(template, fromDate, toDate); + List results = super.executeQuery(query, Aggregators.GROUP_EXCEPTION_OVERVIEW_AGGREGATOR); + List aggResults = new ArrayList(); + for (ExceptionSensorData exData : results) { + if (exData instanceof AggregatedExceptionSensorData) { + aggResults.add((AggregatedExceptionSensorData) exData); + } + } + return aggResults; + } + + /** + * {@inheritDoc} + */ + @Override + public List getStackTraceMessagesForThrowableType(ExceptionSensorData template) { + IIndexQuery query = exceptionSensorDataQueryFactory.getStackTraceMessagesForThrowableTypeQuery(template); + return super.executeQuery(query, Aggregators.DISTINCT_STACK_TRACES_AGGREGATOR); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferHttpTimerDataDaoImpl.java b/CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferHttpTimerDataDaoImpl.java new file mode 100644 index 000000000..620d86c17 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferHttpTimerDataDaoImpl.java @@ -0,0 +1,65 @@ +package info.novatec.inspectit.cmr.dao.impl; + +import info.novatec.inspectit.cmr.dao.HttpTimerDataDao; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.indexing.IIndexQuery; +import info.novatec.inspectit.indexing.aggregation.impl.HttpTimerDataAggregator; +import info.novatec.inspectit.indexing.query.factory.impl.HttpTimerDataQueryFactory; + +import java.util.Date; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +/** + * Provides HttpTimerData information from the CMR internal in memory buffer. + * + * @author Stefan Siegl + */ +@Repository +public class BufferHttpTimerDataDaoImpl extends AbstractBufferDataDao implements HttpTimerDataDao { + + /** + * Index query factory. + */ + @Autowired + private HttpTimerDataQueryFactory httpDataQueryFactory; + + /** + * {@inheritDoc} + */ + @Override + public List getAggregatedHttpTimerData(HttpTimerData httpData, boolean includeRequestMethod) { + IIndexQuery query = httpDataQueryFactory.getFindAllHttpTimersQuery(httpData, null, null); + return super.executeQuery(query, new HttpTimerDataAggregator(true, includeRequestMethod)); + } + + /** + * {@inheritDoc} + */ + @Override + public List getAggregatedHttpTimerData(HttpTimerData httpData, boolean includeRequestMethod, Date fromDate, Date toDate) { + IIndexQuery query = httpDataQueryFactory.getFindAllHttpTimersQuery(httpData, fromDate, toDate); + return super.executeQuery(query, new HttpTimerDataAggregator(true, includeRequestMethod)); + } + + /** + * {@inheritDoc} + */ + @Override + public List getTaggedAggregatedHttpTimerData(HttpTimerData httpData, boolean includeRequestMethod) { + IIndexQuery query = httpDataQueryFactory.getFindAllTaggedHttpTimersQuery(httpData, null, null); + return super.executeQuery(query, new HttpTimerDataAggregator(false, includeRequestMethod)); + } + + /** + * {@inheritDoc} + */ + @Override + public List getTaggedAggregatedHttpTimerData(HttpTimerData httpData, boolean includeRequestMethod, Date fromDate, Date toDate) { + IIndexQuery query = httpDataQueryFactory.getFindAllTaggedHttpTimersQuery(httpData, fromDate, toDate); + return super.executeQuery(query, new HttpTimerDataAggregator(false, includeRequestMethod)); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferInvocationDataDaoImpl.java b/CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferInvocationDataDaoImpl.java new file mode 100644 index 000000000..dc1b3b9f8 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferInvocationDataDaoImpl.java @@ -0,0 +1,100 @@ +package info.novatec.inspectit.cmr.dao.impl; + +import info.novatec.inspectit.cmr.dao.InvocationDataDao; +import info.novatec.inspectit.communication.comparator.DefaultDataComparatorEnum; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.indexing.IIndexQuery; +import info.novatec.inspectit.indexing.query.factory.impl.InvocationSequenceDataQueryFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +/** + * Implementation of {@link InvocationDataDao} that works with the data from the buffer indexing + * tree. + * + * @author Ivan Senic + * + */ +@Repository +public class BufferInvocationDataDaoImpl extends AbstractBufferDataDao implements InvocationDataDao { + + /** + * Index query provider. + */ + @Autowired + private InvocationSequenceDataQueryFactory invocationDataQueryFactory; + + /** + * {@inheritDoc} + */ + public List getInvocationSequenceOverview(long platformId, long methodId, int limit, Comparator comparator) { + return this.getInvocationSequenceOverview(platformId, methodId, limit, null, null, comparator); + } + + /** + * {@inheritDoc} + */ + public List getInvocationSequenceOverview(long platformId, int limit, Comparator comparator) { + return this.getInvocationSequenceOverview(platformId, 0, limit, comparator); + } + + /** + * {@inheritDoc} + */ + public List getInvocationSequenceOverview(long platformId, int limit, Date fromDate, Date toDate, Comparator comparator) { + return this.getInvocationSequenceOverview(platformId, 0, limit, fromDate, toDate, comparator); + } + + /** + * {@inheritDoc} + */ + public List getInvocationSequenceOverview(long platformId, long methodId, int limit, Date fromDate, Date toDate, Comparator comparator) { + IIndexQuery query = invocationDataQueryFactory.getInvocationSequenceOverview(platformId, methodId, limit, fromDate, toDate); + List resultWithChildren; + if (null != comparator) { + resultWithChildren = super.executeQuery(query, comparator, limit); + } else { + resultWithChildren = super.executeQuery(query, DefaultDataComparatorEnum.TIMESTAMP, limit); + } + List realResults = new ArrayList(resultWithChildren.size()); + for (InvocationSequenceData invocationSequenceData : resultWithChildren) { + realResults.add(invocationSequenceData.getClonedInvocationSequence()); + } + return realResults; + + } + + /** + * {@inheritDoc} + */ + @Override + public List getInvocationSequenceOverview(long platformId, Collection invocationIdCollection, int limit, Comparator comparator) { + IIndexQuery query = invocationDataQueryFactory.getInvocationSequenceOverview(platformId, invocationIdCollection, limit); + List resultWithChildren; + if (null != comparator) { + resultWithChildren = super.executeQuery(query, comparator, limit); + } else { + resultWithChildren = super.executeQuery(query, DefaultDataComparatorEnum.TIMESTAMP, limit); + } + List realResults = new ArrayList(resultWithChildren.size()); + for (InvocationSequenceData invocationSequenceData : resultWithChildren) { + realResults.add(invocationSequenceData.getClonedInvocationSequence()); + } + return realResults; + } + + /** + * {@inheritDoc} + */ + public InvocationSequenceData getInvocationSequenceDetail(InvocationSequenceData template) { + return super.getIndexingTree().get(template); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferSqlDataDaoImpl.java b/CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferSqlDataDaoImpl.java new file mode 100644 index 000000000..245d157c2 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferSqlDataDaoImpl.java @@ -0,0 +1,61 @@ +package info.novatec.inspectit.cmr.dao.impl; + +import info.novatec.inspectit.cmr.dao.SqlDataDao; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.indexing.IIndexQuery; +import info.novatec.inspectit.indexing.aggregation.Aggregators; +import info.novatec.inspectit.indexing.query.factory.impl.SqlStatementDataQueryFactory; + +import java.util.Date; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +/** + * Implementation of the {@link SqlDataDao} that searches for the SQL statements in the indexing + * tree. + * + * @author Ivan Senic + * + */ +@Repository +public class BufferSqlDataDaoImpl extends AbstractBufferDataDao implements SqlDataDao { + + /** + * Index query provider. + */ + @Autowired + private SqlStatementDataQueryFactory sqlDataQueryFactory; + + /** + * {@inheritDoc} + */ + public List getAggregatedSqlStatements(SqlStatementData sqlStatementData) { + return this.getAggregatedSqlStatements(sqlStatementData, null, null); + } + + /** + * {@inheritDoc} + */ + public List getAggregatedSqlStatements(SqlStatementData sqlStatementData, Date fromDate, Date toDate) { + IIndexQuery query = sqlDataQueryFactory.getAggregatedSqlStatementsQuery(sqlStatementData, fromDate, toDate); + return super.executeQuery(query, Aggregators.SQL_STATEMENT_DATA_AGGREGATOR); + } + + /** + * {@inheritDoc} + */ + public List getParameterAggregatedSqlStatements(SqlStatementData sqlStatementData) { + return this.getParameterAggregatedSqlStatements(sqlStatementData, null, null); + } + + /** + * {@inheritDoc} + */ + public List getParameterAggregatedSqlStatements(SqlStatementData sqlStatementData, Date fromDate, Date toDate) { + IIndexQuery query = sqlDataQueryFactory.getAggregatedSqlStatementsQuery(sqlStatementData, fromDate, toDate); + return super.executeQuery(query, Aggregators.SQL_STATEMENT_DATA_PARAMETER_AGGREGATOR); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferTimerDataDaoImpl.java b/CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferTimerDataDaoImpl.java new file mode 100644 index 000000000..8a61d8601 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/impl/BufferTimerDataDaoImpl.java @@ -0,0 +1,44 @@ +package info.novatec.inspectit.cmr.dao.impl; + +import info.novatec.inspectit.cmr.dao.TimerDataDao; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.indexing.IIndexQuery; +import info.novatec.inspectit.indexing.aggregation.Aggregators; +import info.novatec.inspectit.indexing.query.factory.impl.TimerDataQueryFactory; + +import java.util.Date; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +/** + * Implementation of {@link TimerData} that searches for timer data in buffer. + * + * @author Ivan Senic + * + */ +@Repository +public class BufferTimerDataDaoImpl extends AbstractBufferDataDao implements TimerDataDao { + + /** + * Index query factory. + */ + @Autowired + private TimerDataQueryFactory timerDataQueryFactory; + + /** + * {@inheritDoc} + */ + public List getAggregatedTimerData(TimerData timerData) { + return this.getAggregatedTimerData(timerData, null, null); + } + + /** + * {@inheritDoc} + */ + public List getAggregatedTimerData(TimerData timerData, Date fromDate, Date toDate) { + IIndexQuery query = timerDataQueryFactory.getAggregatedTimerDataQuery(timerData, fromDate, toDate); + return super.executeQuery(query, Aggregators.TIMER_DATA_AGGREGATOR); + } +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/impl/DefaultDataDaoImpl.java b/CMR/src/info/novatec/inspectit/cmr/dao/impl/DefaultDataDaoImpl.java new file mode 100644 index 000000000..a7ec2ea54 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/impl/DefaultDataDaoImpl.java @@ -0,0 +1,296 @@ +package info.novatec.inspectit.cmr.dao.impl; + +import info.novatec.inspectit.cmr.dao.DefaultDataDao; +import info.novatec.inspectit.cmr.processor.AbstractCmrDataProcessor; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.spring.logger.Log; + +import java.lang.reflect.Modifier; +import java.sql.Timestamp; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.annotation.Resource; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.ArrayUtils; +import org.hibernate.FetchMode; +import org.hibernate.Query; +import org.hibernate.SessionFactory; +import org.hibernate.StatelessSession; +import org.hibernate.Transaction; +import org.hibernate.criterion.DetachedCriteria; +import org.hibernate.criterion.Projections; +import org.hibernate.criterion.Property; +import org.hibernate.criterion.Restrictions; +import org.hibernate.metadata.ClassMetadata; +import org.hibernate.persister.entity.AbstractEntityPersister; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.orm.hibernate3.HibernateTemplate; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +/** + * The default implementation of the {@link DefaultDataDao} interface by using the + * {@link HibernateDaoSupport} from Spring. + *

+ * Delegates many calls to the {@link HibernateTemplate} returned by the {@link HibernateDaoSupport} + * class. + * + * @author Patrice Bouillet + * @author Eduard Tudenhoefner + * @author Ivan Senic + * @author Stefan Siegl + */ +@Repository +public class DefaultDataDaoImpl extends HibernateDaoSupport implements DefaultDataDao { + + /** The logger of this class. */ + @Log + Logger log; + + /** + * List of processors. + */ + @Autowired + @Resource(name = "cmrDataProcessorList") + // resource must be specified, otherwise all processor all plugged here + private List cmrDataProcessors; + + /** + * This constructor is used to set the {@link SessionFactory} that is needed by + * {@link HibernateDaoSupport}. In a future version it may be useful to go away from the + * {@link HibernateDaoSupport} and directly use the {@link SessionFactory}. This is described + * here: + * http://blog.springsource.com/2007/06/26/so-should-you-still-use-springs-hibernatetemplate + * -andor-jpatemplate + * + * @param sessionFactory + * the hibernate session factory. + */ + @Autowired + public DefaultDataDaoImpl(SessionFactory sessionFactory) { + setSessionFactory(sessionFactory); + } + + /** + * {@inheritDoc} + */ + public void save(DefaultData defaultData) { + getHibernateTemplate().save(defaultData); + } + + /** + * {@inheritDoc} + */ + public void saveAll(List defaultDataCollection) { + StatelessSession session = getHibernateTemplate().getSessionFactory().openStatelessSession(); + Transaction tx = null; + try { + tx = session.beginTransaction(); + for (AbstractCmrDataProcessor processor : cmrDataProcessors) { + processor.process(defaultDataCollection, session); + } + tx.commit(); + } catch (Exception e) { + if (null != tx) { + tx.rollback(); + } + + log.error("Error occurred trying to process the CMR data processors on the incoming data. Transaction rolled back.", e); + } + session.close(); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public List findByExampleWithLastInterval(DefaultData template, long timeInterval) { + DetachedCriteria defaultDataCriteria = DetachedCriteria.forClass(template.getClass()); + defaultDataCriteria.add(Restrictions.eq("platformIdent", template.getPlatformIdent())); + defaultDataCriteria.add(Restrictions.eq("sensorTypeIdent", template.getSensorTypeIdent())); + defaultDataCriteria.add(Restrictions.gt("timeStamp", new Timestamp(System.currentTimeMillis() - timeInterval))); + + if (template instanceof MethodSensorData) { + MethodSensorData methodSensorData = (MethodSensorData) template; + defaultDataCriteria.add(Restrictions.eq("methodIdent", methodSensorData.getMethodIdent())); + defaultDataCriteria.setFetchMode("parameterContentData", FetchMode.JOIN); + } + + defaultDataCriteria.setResultTransformer(DetachedCriteria.DISTINCT_ROOT_ENTITY); + return getHibernateTemplate().findByCriteria(defaultDataCriteria); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public List findByExampleSinceId(DefaultData template) { + DetachedCriteria defaultDataCriteria = DetachedCriteria.forClass(template.getClass()); + defaultDataCriteria.add(Restrictions.gt("id", template.getId())); + defaultDataCriteria.add(Restrictions.eq("platformIdent", template.getPlatformIdent())); + defaultDataCriteria.add(Restrictions.eq("sensorTypeIdent", template.getSensorTypeIdent())); + + if (template instanceof MethodSensorData) { + MethodSensorData methodSensorData = (MethodSensorData) template; + defaultDataCriteria.add(Restrictions.eq("methodIdent", methodSensorData.getMethodIdent())); + defaultDataCriteria.setFetchMode("parameterContentData", FetchMode.JOIN); + } + + defaultDataCriteria.setResultTransformer(DetachedCriteria.DISTINCT_ROOT_ENTITY); + return getHibernateTemplate().findByCriteria(defaultDataCriteria); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public List findByExampleSinceIdIgnoreMethodId(DefaultData template) { + DetachedCriteria defaultDataCriteria = DetachedCriteria.forClass(template.getClass()); + defaultDataCriteria.add(Restrictions.gt("id", template.getId())); + defaultDataCriteria.add(Restrictions.eq("platformIdent", template.getPlatformIdent())); + + defaultDataCriteria.setResultTransformer(DetachedCriteria.DISTINCT_ROOT_ENTITY); + return getHibernateTemplate().findByCriteria(defaultDataCriteria); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public List findByExampleFromToDate(DefaultData template, Date fromDate, Date toDate) { + DetachedCriteria defaultDataCriteria = DetachedCriteria.forClass(template.getClass()); + defaultDataCriteria.add(Restrictions.eq("platformIdent", template.getPlatformIdent())); + defaultDataCriteria.add(Restrictions.eq("sensorTypeIdent", template.getSensorTypeIdent())); + defaultDataCriteria.add(Restrictions.between("timeStamp", new Timestamp(fromDate.getTime()), new Timestamp(toDate.getTime()))); + + if (template instanceof MethodSensorData) { + MethodSensorData methodSensorData = (MethodSensorData) template; + defaultDataCriteria.add(Restrictions.eq("methodIdent", methodSensorData.getMethodIdent())); + defaultDataCriteria.setFetchMode("parameterContentData", FetchMode.JOIN); + } + + defaultDataCriteria.setResultTransformer(DetachedCriteria.DISTINCT_ROOT_ENTITY); + return getHibernateTemplate().findByCriteria(defaultDataCriteria); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Transactional + public DefaultData findByExampleLastData(DefaultData template) { + DetachedCriteria subQuery = DetachedCriteria.forClass(template.getClass()); + subQuery.add(Restrictions.eq("platformIdent", template.getPlatformIdent())); + subQuery.setProjection(Projections.projectionList().add(Projections.max("id"))); + + DetachedCriteria defaultDataCriteria = DetachedCriteria.forClass(template.getClass()); + if (template instanceof MethodSensorData) { + MethodSensorData methodSensorData = (MethodSensorData) template; + subQuery.add(Restrictions.eq("methodIdent", methodSensorData.getMethodIdent())); + defaultDataCriteria.setFetchMode("parameterContentData", FetchMode.JOIN); + } + defaultDataCriteria.add(Property.forName("id").eq(subQuery)); + defaultDataCriteria.setResultTransformer(DetachedCriteria.DISTINCT_ROOT_ENTITY); + + List resultList = getHibernateTemplate().findByCriteria(defaultDataCriteria); + if (CollectionUtils.isNotEmpty(resultList)) { + return resultList.get(0); + } else { + return null; + } + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public List getChartingHttpTimerDataFromDateToDate(Collection templates, Date fromDate, Date toDate, boolean retrieveByTag) { + if (CollectionUtils.isNotEmpty(templates)) { + DetachedCriteria criteria = DetachedCriteria.forClass(HttpTimerData.class); + criteria.add(Restrictions.eq("platformIdent", templates.iterator().next().getPlatformIdent())); + criteria.add(Restrictions.between("timeStamp", new Timestamp(fromDate.getTime()), new Timestamp(toDate.getTime()))); + + if (!retrieveByTag) { + Set uris = new HashSet(); + for (HttpTimerData httpTimerData : templates) { + if (!HttpTimerData.UNDEFINED.equals(httpTimerData.getUri())) { + uris.add(httpTimerData.getUri()); + } + } + criteria.add(Restrictions.in("uri", uris)); + } else { + Set tags = new HashSet(); + + for (HttpTimerData httpTimerData : templates) { + if (httpTimerData.hasInspectItTaggingHeader()) { + tags.add(httpTimerData.getInspectItTaggingHeaderValue()); + } + } + criteria.add(Restrictions.in("inspectItTaggingHeaderValue", tags)); + } + + criteria.setResultTransformer(DetachedCriteria.DISTINCT_ROOT_ENTITY); + return getHibernateTemplate().findByCriteria(criteria); + } else { + return Collections.emptyList(); + } + } + + /** + * {@inheritDoc} + */ + public void deleteAll(Long platformId) { + // because H2 does not support cascading on delete we need to clear all connected data + Query query = getSession().createQuery("delete from VmArgumentData where systemInformationId in (select id from SystemInformationData where platformIdent = :platformIdent)"); + query.setLong("platformIdent", platformId); + query.executeUpdate(); + + // the code below is the workaround the Hibernate batch delete problem of all DefaultData + // instances + // any batch delete or delete executed on the abstract class will raise problem with not + // existing temporary tables + Map map = getSessionFactory().getAllClassMetadata(); + for (Entry entry : map.entrySet()) { + // for each mapped class check: + // * that is not abstract + // * that it's a default data subclass + // * that has platfromIdent property + ClassMetadata classMetadata = entry.getValue(); + String className = entry.getKey(); + Class clazz = null; + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException e) { + continue; + } + boolean isAbstract = Modifier.isAbstract(clazz.getModifiers()); + if (!isAbstract && classMetadata instanceof AbstractEntityPersister) { + isAbstract = ((AbstractEntityPersister) classMetadata).isAbstract(); + } + + if (!isAbstract && DefaultData.class.isAssignableFrom(clazz)) { + boolean hasPlatformIdent = ArrayUtils.contains(classMetadata.getPropertyNames(), "platformIdent"); + if (hasPlatformIdent) { + // then delete that default data + query = getSession().createQuery("delete from " + className + " where platformIdent = :platformIdent"); + query.setLong("platformIdent", platformId); + query.executeUpdate(); + } + } + } + + } +} \ No newline at end of file diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/impl/MethodIdentDaoImpl.java b/CMR/src/info/novatec/inspectit/cmr/dao/impl/MethodIdentDaoImpl.java new file mode 100644 index 000000000..f557e2dcb --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/impl/MethodIdentDaoImpl.java @@ -0,0 +1,121 @@ +package info.novatec.inspectit.cmr.dao.impl; + +import info.novatec.inspectit.cmr.dao.MethodIdentDao; +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.cmr.util.PlatformIdentCache; + +import java.util.List; + +import org.hibernate.FetchMode; +import org.hibernate.SessionFactory; +import org.hibernate.criterion.DetachedCriteria; +import org.hibernate.criterion.Restrictions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.orm.hibernate3.HibernateTemplate; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; +import org.springframework.stereotype.Repository; + +/** + * The default implementation of the {@link MethodIdentDao} interface by using the + * {@link HibernateDaoSupport} from Spring. + *

+ * Delegates many calls to the {@link HibernateTemplate} returned by the {@link HibernateDaoSupport} + * class. + * + * @author Patrice Bouillet + * + */ +@Repository +public class MethodIdentDaoImpl extends HibernateDaoSupport implements MethodIdentDao { + + /** + * This constructor is used to set the {@link SessionFactory} that is needed by + * {@link HibernateDaoSupport}. In a future version it may be useful to go away from the + * {@link HibernateDaoSupport} and directly use the {@link SessionFactory}. This is described + * here: + * http://blog.springsource.com/2007/06/26/so-should-you-still-use-springs-hibernatetemplate + * -andor-jpatemplate + * + * @param sessionFactory + * the hibernate session factory. + */ + @Autowired + public MethodIdentDaoImpl(SessionFactory sessionFactory) { + setSessionFactory(sessionFactory); + } + + /** + * {@link PlatformIdent} cache. + */ + @Autowired + private PlatformIdentCache platformIdentCache; + + /** + * {@inheritDoc} + */ + public void delete(MethodIdent methodIdent) { + getHibernateTemplate().delete(methodIdent); + } + + /** + * {@inheritDoc} + */ + public void deleteAll(List methodIdents) { + getHibernateTemplate().deleteAll(methodIdents); + } + + /** + * {@inheritDoc} + */ + public List findAll() { + return getHibernateTemplate().loadAll(MethodIdent.class); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public List findByExample(MethodIdent methodIdent) { + return getHibernateTemplate().findByExample(methodIdent); + } + + /** + * {@inheritDoc} + */ + public MethodIdent load(Long id) { + return (MethodIdent) getHibernateTemplate().get(MethodIdent.class, id); + } + + /** + * {@inheritDoc} + */ + public void saveOrUpdate(MethodIdent methodIdent) { + getHibernateTemplate().saveOrUpdate(methodIdent); + platformIdentCache.markDirty(methodIdent.getPlatformIdent()); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public List findForPlatformIdent(long platformId, MethodIdent methodIdentExample) { + DetachedCriteria methodCriteria = DetachedCriteria.forClass(MethodIdent.class); + if (null == methodIdentExample.getPackageName()) { + methodCriteria.add(Restrictions.isNull("packageName")); + } else { + methodCriteria.add(Restrictions.eq("packageName", methodIdentExample.getPackageName())); + } + methodCriteria.add(Restrictions.eq("className", methodIdentExample.getClassName())); + methodCriteria.add(Restrictions.eq("methodName", methodIdentExample.getMethodName())); + methodCriteria.add(Restrictions.eq("parameters", methodIdentExample.getParameters())); + methodCriteria.add(Restrictions.eq("returnType", methodIdentExample.getReturnType())); + + methodCriteria.setFetchMode("platformIdent", FetchMode.JOIN); + DetachedCriteria platformCriteria = methodCriteria.createCriteria("platformIdent"); + platformCriteria.add(Restrictions.eq("id", platformId)); + + return getHibernateTemplate().findByCriteria(methodCriteria); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/impl/MethodIdentToSensorTypeDaoImpl.java b/CMR/src/info/novatec/inspectit/cmr/dao/impl/MethodIdentToSensorTypeDaoImpl.java new file mode 100644 index 000000000..70e50b9b6 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/impl/MethodIdentToSensorTypeDaoImpl.java @@ -0,0 +1,75 @@ +package info.novatec.inspectit.cmr.dao.impl; + +import info.novatec.inspectit.cmr.dao.MethodIdentToSensorTypeDao; +import info.novatec.inspectit.cmr.model.MethodIdentToSensorType; + +import java.util.List; + +import org.hibernate.SessionFactory; +import org.hibernate.criterion.DetachedCriteria; +import org.hibernate.criterion.Restrictions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.orm.hibernate3.HibernateTemplate; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; +import org.springframework.stereotype.Repository; + +/** + * The default implementation of the {@link MethodIdentToSensorTypeDao} interface by using the + * {@link HibernateDaoSupport} from Spring. + *

+ * Delegates many calls to the {@link HibernateTemplate} returned by the {@link HibernateDaoSupport} + * class. + * + * @author Ivan Senic + * + */ +@Repository +public class MethodIdentToSensorTypeDaoImpl extends HibernateDaoSupport implements MethodIdentToSensorTypeDao { + + /** + * This constructor is used to set the {@link SessionFactory} that is needed by + * {@link HibernateDaoSupport}. In a future version it may be useful to go away from the + * {@link HibernateDaoSupport} and directly use the {@link SessionFactory}. This is described + * here: + * http://blog.springsource.com/2007/06/26/so-should-you-still-use-springs-hibernatetemplate + * -andor-jpatemplate + * + * @param sessionFactory + * the hibernate session factory. + */ + @Autowired + public MethodIdentToSensorTypeDaoImpl(SessionFactory sessionFactory) { + setSessionFactory(sessionFactory); + } + + /** + * {@inheritDoc} + */ + public MethodIdentToSensorType load(Long id) { + return getHibernateTemplate().get(MethodIdentToSensorType.class, id); + } + + /** + * {@inheritDoc} + */ + public void saveOrUpdate(MethodIdentToSensorType methodIdentToSensorType) { + getHibernateTemplate().saveOrUpdate(methodIdentToSensorType); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public MethodIdentToSensorType find(long methodIdentId, long methodSensorTypeIdentId) { + DetachedCriteria detachedCriteria = DetachedCriteria.forClass(MethodIdentToSensorType.class); + detachedCriteria.add(Restrictions.eq("methodIdent.id", methodIdentId)); + detachedCriteria.add(Restrictions.eq("methodSensorTypeIdent.id", methodSensorTypeIdentId)); + detachedCriteria.setResultTransformer(DetachedCriteria.DISTINCT_ROOT_ENTITY); + List list = getHibernateTemplate().findByCriteria(detachedCriteria, 0, 1); + if (list.size() == 1) { + return list.get(0); + } else { + return null; + } + } +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/impl/MethodSensorTypeIdentDaoImpl.java b/CMR/src/info/novatec/inspectit/cmr/dao/impl/MethodSensorTypeIdentDaoImpl.java new file mode 100644 index 000000000..91c03bdbb --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/impl/MethodSensorTypeIdentDaoImpl.java @@ -0,0 +1,93 @@ +package info.novatec.inspectit.cmr.dao.impl; + +import info.novatec.inspectit.cmr.dao.MethodSensorTypeIdentDao; +import info.novatec.inspectit.cmr.model.MethodSensorTypeIdent; + +import java.util.List; + +import org.hibernate.SessionFactory; +import org.hibernate.criterion.DetachedCriteria; +import org.hibernate.criterion.Example; +import org.hibernate.criterion.Restrictions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.orm.hibernate3.HibernateTemplate; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; +import org.springframework.stereotype.Repository; + +/** + * The default implementation of the {@link MethodSensorTypeIdentDao} interface by using the + * {@link HibernateDaoSupport} from Spring. + *

+ * Delegates many calls to the {@link HibernateTemplate} returned by the {@link HibernateDaoSupport} + * class. + * + * @author Patrice Bouillet + * + */ +@Repository +public class MethodSensorTypeIdentDaoImpl extends HibernateDaoSupport implements MethodSensorTypeIdentDao { + + /** + * This constructor is used to set the {@link SessionFactory} that is needed by + * {@link HibernateDaoSupport}. In a future version it may be useful to go away from the + * {@link HibernateDaoSupport} and directly use the {@link SessionFactory}. This is described + * here: + * http://blog.springsource.com/2007/06/26/so-should-you-still-use-springs-hibernatetemplate + * -andor-jpatemplate + * + * @param sessionFactory + * the hibernate session factory. + */ + @Autowired + public MethodSensorTypeIdentDaoImpl(SessionFactory sessionFactory) { + setSessionFactory(sessionFactory); + } + + /** + * {@inheritDoc} + */ + public void delete(MethodSensorTypeIdent methodSensorTypeIdent) { + getHibernateTemplate().delete(methodSensorTypeIdent); + } + + /** + * {@inheritDoc} + */ + public void deleteAll(List methodSensorTypeIdents) { + getHibernateTemplate().deleteAll(methodSensorTypeIdents); + } + + /** + * {@inheritDoc} + */ + public List findAll() { + return getHibernateTemplate().loadAll(MethodSensorTypeIdent.class); + } + + /** + * {@inheritDoc} + */ + public MethodSensorTypeIdent load(Long id) { + return (MethodSensorTypeIdent) getHibernateTemplate().get(MethodSensorTypeIdent.class, id); + } + + /** + * {@inheritDoc} + */ + public void saveOrUpdate(MethodSensorTypeIdent methodSensorTypeIdent) { + getHibernateTemplate().saveOrUpdate(methodSensorTypeIdent); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public List findByExample(long platformId, MethodSensorTypeIdent methodSensorTypeIdent) { + DetachedCriteria detachedCriteria = DetachedCriteria.forClass(methodSensorTypeIdent.getClass()); + detachedCriteria.add(Example.create(methodSensorTypeIdent)); + detachedCriteria.add(Restrictions.eq("platformIdent.id", platformId)); + detachedCriteria.setResultTransformer(DetachedCriteria.DISTINCT_ROOT_ENTITY); + return getHibernateTemplate().findByCriteria(detachedCriteria); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/impl/PlatformIdentDaoImpl.java b/CMR/src/info/novatec/inspectit/cmr/dao/impl/PlatformIdentDaoImpl.java new file mode 100644 index 000000000..59042f3a3 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/impl/PlatformIdentDaoImpl.java @@ -0,0 +1,259 @@ +package info.novatec.inspectit.cmr.dao.impl; + +import info.novatec.inspectit.cmr.dao.PlatformIdentDao; +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.cmr.util.PlatformIdentCache; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import javax.annotation.PostConstruct; + +import org.apache.commons.collections.CollectionUtils; +import org.hibernate.Query; +import org.hibernate.SessionFactory; +import org.hibernate.criterion.DetachedCriteria; +import org.hibernate.criterion.Order; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.orm.hibernate3.HibernateTemplate; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; +import org.springframework.stereotype.Repository; + +/** + * The default implementation of the {@link PlatformIdentDao} interface by using the + * {@link HibernateDaoSupport} from Spring. + *

+ * Delegates many calls to the {@link HibernateTemplate} returned by the {@link HibernateDaoSupport} + * class. + * + * @author Patrice Bouillet + * + */ +@Repository +public class PlatformIdentDaoImpl extends HibernateDaoSupport implements PlatformIdentDao { + + /** + * {@link PlatformIdent} cache. + */ + @Autowired + private PlatformIdentCache platformIdentCache; + + /** + * This constructor is used to set the {@link SessionFactory} that is needed by + * {@link HibernateDaoSupport}. In a future version it may be useful to go away from the + * {@link HibernateDaoSupport} and directly use the {@link SessionFactory}. This is described + * here: + * http://blog.springsource.com/2007/06/26/so-should-you-still-use-springs-hibernatetemplate + * -andor-jpatemplate + * + * @param sessionFactory + * the hibernate session factory. + */ + @Autowired + public PlatformIdentDaoImpl(SessionFactory sessionFactory) { + setSessionFactory(sessionFactory); + } + + /** + * {@inheritDoc} + */ + public void delete(PlatformIdent platformIdent) { + getHibernateTemplate().delete(platformIdent); + platformIdentCache.remove(platformIdent); + } + + /** + * {@inheritDoc} + */ + public void deleteAll(List platformIdents) { + for (PlatformIdent platformIdent : platformIdents) { + delete(platformIdent); + } + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public List findAll() { + DetachedCriteria criteria = DetachedCriteria.forClass(PlatformIdent.class); + criteria.addOrder(Order.asc("agentName")); + criteria.setResultTransformer(DetachedCriteria.DISTINCT_ROOT_ENTITY); + return getHibernateTemplate().findByCriteria(criteria); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public List findByExample(PlatformIdent platformIdent) { + return getHibernateTemplate().findByExample(platformIdent); + } + + /** + * {@inheritDoc} + */ + public PlatformIdent load(Long id) { + return (PlatformIdent) getHibernateTemplate().get(PlatformIdent.class, id); + } + + /** + * {@inheritDoc} + */ + public void saveOrUpdate(PlatformIdent platformIdent) { + final int maxDefIPsSize = 1024; + + if (null != platformIdent.getDefinedIPs()) { + int charNum = 0; + List newDefinedIPs = new ArrayList(); + for (String item : platformIdent.getDefinedIPs()) { + // if it is too long, we stop adding + if (charNum + item.length() <= maxDefIPsSize) { + newDefinedIPs.add(item); + // we add 1 also for the white space + charNum += item.length() + 1; + } else { + break; + } + } + + // change only if we really cut the list + if (newDefinedIPs.size() != platformIdent.getDefinedIPs().size()) { + platformIdent.setDefinedIPs(newDefinedIPs); + } + } + + getHibernateTemplate().saveOrUpdate(platformIdent); + platformIdentCache.markDirty(platformIdent); + } + + /** + * {@inheritDoc} + */ + public void evict(PlatformIdent platformIdent) { + getHibernateTemplate().evict(platformIdent); + } + + /** + * {@inheritDoc} + */ + public void evictAll(List platformIdents) { + for (PlatformIdent platformIdent : platformIdents) { + this.evict(platformIdent); + } + } + + /** + * {@inheritDoc} + */ + public PlatformIdent findInitialized(long id) { + for (PlatformIdent platformIdent : platformIdentCache.getCleanPlatformIdents()) { + if (platformIdent.getId().longValue() == id) { + return platformIdent; + } + } + + List cleanPlatformIdents = loadIdentsFromDB(Collections. emptyList(), Collections.singleton(Long.valueOf(id))); + if (CollectionUtils.isNotEmpty(cleanPlatformIdents)) { + if (1 == cleanPlatformIdents.size()) { + return cleanPlatformIdents.get(0); + } else { + throw new RuntimeException("More than one agent retrieved for one ID."); + } + } + + return null; + } + + /** + * Find all initialized agents that have a id in a given set. + * + * @param wantedAgentsIds + * Agents Ids. + * @return List of {@link PlatformIdent}. + */ + public List findAllInitialized(Set wantedAgentsIds) { + if (null == wantedAgentsIds) { + return Collections.emptyList(); + } + + List initializedPlatformIdents = new ArrayList(); + List cleanIdents = new ArrayList(); + for (PlatformIdent platformIdent : platformIdentCache.getCleanPlatformIdents()) { + cleanIdents.add(platformIdent.getId()); + if (wantedAgentsIds.contains(platformIdent.getId())) { + initializedPlatformIdents.add(platformIdent); + } + } + + wantedAgentsIds.removeAll(cleanIdents); + if (cleanIdents.size() != platformIdentCache.getSize()) { + List cleanPlatformIdents = loadIdentsFromDB(cleanIdents, wantedAgentsIds); + for (PlatformIdent platformIdent : cleanPlatformIdents) { + if (wantedAgentsIds.contains(platformIdent.getId())) { + initializedPlatformIdents.add(platformIdent); + } + } + } + + Collections.sort(initializedPlatformIdents, new Comparator() { + @Override + public int compare(PlatformIdent o1, PlatformIdent o2) { + return (int) (o1.getId().longValue() - o2.getId().longValue()); + } + }); + return initializedPlatformIdents; + } + + /** + * Initialize all platform idents from the database. + */ + @PostConstruct + public void postConstruct() { + loadIdentsFromDB(Collections. emptyList(), Collections. emptyList()); + } + + /** + * Loads agents from database, excluding the agents which IDs is supplied in the exclude + * collection. + * + * @param excludeIdents + * IDs of the agents that should not be loaded. If empty or null it + * won't be taken into consideration. + * @param includeIdents + * IDs of the agents that should be loaded. If empty or null it won't be + * taken into consideration. + * + * @return List of {@link PlatformIdent}. + */ + @SuppressWarnings("unchecked") + private List loadIdentsFromDB(Collection excludeIdents, Collection includeIdents) { + StringBuilder hsql = new StringBuilder( + "select distinct platformIdent from PlatformIdent as platformIdent left join fetch platformIdent.methodIdents methodIdent left join fetch platformIdent.sensorTypeIdents left join fetch methodIdent.methodIdentToSensorTypes"); + if (CollectionUtils.isNotEmpty(includeIdents) && CollectionUtils.isNotEmpty(excludeIdents)) { + hsql.append(" where platformIdent.id in :includeIdents and platformIdent.id not in :excludeIdents"); + } else if (CollectionUtils.isNotEmpty(includeIdents)) { + hsql.append(" where platformIdent.id in :includeIdents"); + } else if (CollectionUtils.isNotEmpty(excludeIdents)) { + hsql.append(" where platformIdent.id not in :excludeIdents"); + } + + Query query = getSession().createQuery(hsql.toString()); + if (CollectionUtils.isNotEmpty(includeIdents)) { + query.setParameterList("includeIdents", includeIdents); + } + if (CollectionUtils.isNotEmpty(excludeIdents)) { + query.setParameterList("excludeIdents", excludeIdents); + } + + List platformIdents = query.list(); + for (PlatformIdent platformIdent : platformIdents) { + platformIdentCache.markClean(platformIdent); + } + return platformIdents; + } +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/impl/PlatformSensorTypeIdentDaoImpl.java b/CMR/src/info/novatec/inspectit/cmr/dao/impl/PlatformSensorTypeIdentDaoImpl.java new file mode 100644 index 000000000..05dd90b2f --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/impl/PlatformSensorTypeIdentDaoImpl.java @@ -0,0 +1,93 @@ +package info.novatec.inspectit.cmr.dao.impl; + +import info.novatec.inspectit.cmr.dao.PlatformSensorTypeIdentDao; +import info.novatec.inspectit.cmr.model.PlatformSensorTypeIdent; + +import java.util.List; + +import org.hibernate.SessionFactory; +import org.hibernate.criterion.DetachedCriteria; +import org.hibernate.criterion.Example; +import org.hibernate.criterion.Restrictions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.orm.hibernate3.HibernateTemplate; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; +import org.springframework.stereotype.Repository; + +/** + * The default implementation of the {@link PlatformSensorTypeIdentDao} interface by using the + * {@link HibernateDaoSupport} from Spring. + *

+ * Delegates many calls to the {@link HibernateTemplate} returned by the {@link HibernateDaoSupport} + * class. + * + * @author Patrice Bouillet + * + */ +@Repository +public class PlatformSensorTypeIdentDaoImpl extends HibernateDaoSupport implements PlatformSensorTypeIdentDao { + + /** + * This constructor is used to set the {@link SessionFactory} that is needed by + * {@link HibernateDaoSupport}. In a future version it may be useful to go away from the + * {@link HibernateDaoSupport} and directly use the {@link SessionFactory}. This is described + * here: + * http://blog.springsource.com/2007/06/26/so-should-you-still-use-springs-hibernatetemplate + * -andor-jpatemplate + * + * @param sessionFactory + * the hibernate session factory. + */ + @Autowired + public PlatformSensorTypeIdentDaoImpl(SessionFactory sessionFactory) { + setSessionFactory(sessionFactory); + } + + /** + * {@inheritDoc} + */ + public void delete(PlatformSensorTypeIdent platformSensorTypeIdent) { + getHibernateTemplate().delete(platformSensorTypeIdent); + } + + /** + * {@inheritDoc} + */ + public void deleteAll(List platformSensorTypeIdents) { + getHibernateTemplate().deleteAll(platformSensorTypeIdents); + } + + /** + * {@inheritDoc} + */ + public List findAll() { + return getHibernateTemplate().loadAll(PlatformSensorTypeIdent.class); + } + + /** + * {@inheritDoc} + */ + public PlatformSensorTypeIdent load(Long id) { + return (PlatformSensorTypeIdent) getHibernateTemplate().get(PlatformSensorTypeIdent.class, id); + } + + /** + * {@inheritDoc} + */ + public void saveOrUpdate(PlatformSensorTypeIdent platformSensorTypeIdent) { + getHibernateTemplate().saveOrUpdate(platformSensorTypeIdent); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public List findByExample(long platformId, PlatformSensorTypeIdent platformSensorTypeIdent) { + DetachedCriteria detachedCriteria = DetachedCriteria.forClass(platformSensorTypeIdent.getClass()); + detachedCriteria.add(Example.create(platformSensorTypeIdent)); + detachedCriteria.add(Restrictions.eq("platformIdent.id", platformId)); + detachedCriteria.setResultTransformer(DetachedCriteria.DISTINCT_ROOT_ENTITY); + return getHibernateTemplate().findByCriteria(detachedCriteria); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/impl/StorageDataDaoImpl.java b/CMR/src/info/novatec/inspectit/cmr/dao/impl/StorageDataDaoImpl.java new file mode 100644 index 000000000..229ddd0ff --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/impl/StorageDataDaoImpl.java @@ -0,0 +1,285 @@ +package info.novatec.inspectit.cmr.dao.impl; + +import info.novatec.inspectit.cmr.dao.StorageDataDao; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.SystemSensorData; +import info.novatec.inspectit.communication.data.SystemInformationData; +import info.novatec.inspectit.indexing.IIndexQuery; +import info.novatec.inspectit.indexing.buffer.IBufferTreeComponent; +import info.novatec.inspectit.indexing.impl.IndexingException; +import info.novatec.inspectit.indexing.query.provider.impl.IndexQueryProvider; +import info.novatec.inspectit.indexing.restriction.impl.IndexQueryRestrictionFactory; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.label.StringStorageLabel; +import info.novatec.inspectit.storage.label.type.AbstractStorageLabelType; +import info.novatec.inspectit.storage.label.type.impl.AssigneeLabelType; +import info.novatec.inspectit.storage.label.type.impl.RatingLabelType; +import info.novatec.inspectit.storage.label.type.impl.StatusLabelType; +import info.novatec.inspectit.storage.label.type.impl.UseCaseLabelType; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.apache.commons.collections.CollectionUtils; +import org.hibernate.Criteria; +import org.hibernate.FetchMode; +import org.hibernate.SessionFactory; +import org.hibernate.criterion.DetachedCriteria; +import org.hibernate.criterion.Projections; +import org.hibernate.criterion.Property; +import org.hibernate.criterion.Restrictions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; +import org.springframework.stereotype.Repository; + +/** + * Dao support for the storage purposes.. + * + * @author Ivan Senic + * + */ +@Repository +public class StorageDataDaoImpl extends HibernateDaoSupport implements StorageDataDao { + + /** + * {@link IndexQueryProvider}. + */ + @Autowired + private IndexQueryProvider indexQueryProvider; + + /** + * {@link IndexingException} tree. + */ + @Autowired + private IBufferTreeComponent indexingTree; + + /** + * This constructor is used to set the {@link SessionFactory} that is needed by + * {@link HibernateDaoSupport}. In a future version it may be useful to go away from the + * {@link HibernateDaoSupport} and directly use the {@link SessionFactory}. This is described + * here: + * http://blog.springsource.com/2007/06/26/so-should-you-still-use-springs-hibernatetemplate + * -andor-jpatemplate + * + * @param sessionFactory + * {@link SessionFactory} + */ + @Autowired + public StorageDataDaoImpl(SessionFactory sessionFactory) { + setSessionFactory(sessionFactory); + } + + /** + * {@inheritDoc} + */ + public boolean saveLabel(AbstractStorageLabel label) { + if (label.getStorageLabelType().isValueReusable()) { + List exampleFind = getHibernateTemplate().findByExample(label); + if (!exampleFind.contains(label)) { + AbstractStorageLabelType labelType = label.getStorageLabelType(); + if (null == labelType) { + return false; + } + if (labelType.getId() == 0 && !labelType.isMultiType()) { + return false; + } + getHibernateTemplate().saveOrUpdate(label); + return true; + } + } + return false; + } + + /** + * {@inheritDoc} + */ + public void removeLabel(AbstractStorageLabel label) { + if (label.getStorageLabelType().isValueReusable()) { + getHibernateTemplate().delete(label); + } + } + + /** + * {@inheritDoc} + */ + public void removeLabels(Collection> labels) { + for (AbstractStorageLabel label : labels) { + this.removeLabel(label); + } + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public List> getAllLabels() { + List allLabels = getHibernateTemplate().loadAll(AbstractStorageLabel.class); + return (List>) allLabels; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public List> getAllLabelsForType(AbstractStorageLabelType labelType) { + DetachedCriteria criteria = DetachedCriteria.forClass(AbstractStorageLabel.class); + criteria.add(Restrictions.eq("storageLabelType", labelType)); + criteria.setFetchMode("storageLabelType", FetchMode.JOIN); + criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); + return getHibernateTemplate().findByCriteria(criteria); + } + + /** + * {@inheritDoc} + */ + public void saveLabelType(AbstractStorageLabelType labelType) { + if (labelType.isMultiType()) { + getHibernateTemplate().saveOrUpdate(labelType); + } else { + List findByClass = getHibernateTemplate().loadAll(labelType.getClass()); + if (findByClass.isEmpty()) { + getHibernateTemplate().saveOrUpdate(labelType); + } + } + } + + /** + * {@inheritDoc} + */ + public void removeLabelType(AbstractStorageLabelType labelType) throws Exception { + if (getAllLabelsForType(labelType).isEmpty()) { + getHibernateTemplate().delete(labelType); + } else { + throw new Exception("Label type can not be deleted because there are still lables of the type existing. Please first delete all labels of the type."); + } + } + + /** + * {@inheritDoc} + */ + public > List getLabelTypes(Class labelTypeClass) { + return getHibernateTemplate().loadAll(labelTypeClass); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public List> getAllLabelTypes() { + List returnList = getHibernateTemplate().loadAll(AbstractStorageLabelType.class); + return (List>) returnList; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public List getAllDefaultDataForAgent(long platformId, Date fromDate, Date toDate) { + List results = new ArrayList<>(); + + // first load all from buffer + IIndexQuery query = indexQueryProvider.createNewIndexQuery(); + query.setPlatformIdent(platformId); + if (null != fromDate) { + query.setFromDate(new Timestamp(fromDate.getTime())); + } + if (null != toDate) { + query.setToDate(new Timestamp(toDate.getTime())); + } + List bufferData = indexingTree.query(query); + if (CollectionUtils.isNotEmpty(bufferData)) { + results.addAll(bufferData); + } + + // then load all System sensor data from DB + DetachedCriteria criteria = DetachedCriteria.forClass(SystemSensorData.class); + criteria.add(Restrictions.eq("platformIdent", platformId)); + if (null != fromDate) { + criteria.add(Restrictions.ge("timeStamp", new Timestamp(fromDate.getTime()))); + } + if (null != toDate) { + criteria.add(Restrictions.le("timeStamp", new Timestamp(toDate.getTime()))); + } + criteria.setResultTransformer(DetachedCriteria.DISTINCT_ROOT_ENTITY); + List sensorDatas = getHibernateTemplate().findByCriteria(criteria); + + // combine results + if (CollectionUtils.isNotEmpty(sensorDatas)) { + results.addAll(sensorDatas); + } + + // since we only have one system information data per agent connection + // we need to manually add it as we can not be sure that the time stamp of the oldest + // element in the buffer will include the system data send on the agent connection + List systemInformationData = getSystemInformationData(Collections.singletonList(platformId)); + if (CollectionUtils.isNotEmpty(systemInformationData)) { + results.addAll(systemInformationData); + } + + return results; + } + + /** + * {@inheritDoc} + */ + @Override + public List getDataFromIdList(Collection elementIds, long platformIdent) { + IIndexQuery query = indexQueryProvider.createNewIndexQuery(); + query.addIndexingRestriction(IndexQueryRestrictionFactory.isInCollection("id", elementIds)); + query.setPlatformIdent(platformIdent); + return indexingTree.query(query); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public List getSystemInformationData(Collection agentIds) { + DetachedCriteria subQuery = DetachedCriteria.forClass(SystemInformationData.class); + subQuery.add(Restrictions.in("platformIdent", agentIds)); + subQuery.setProjection(Projections.projectionList().add(Projections.max("id"))); + + DetachedCriteria defaultDataCriteria = DetachedCriteria.forClass(SystemInformationData.class); + defaultDataCriteria.add(Property.forName("id").in(subQuery)); + defaultDataCriteria.setResultTransformer(DetachedCriteria.DISTINCT_ROOT_ENTITY); + return getHibernateTemplate().findByCriteria(defaultDataCriteria); + } + + /** + * Create set of default labels. + */ + @PostConstruct + protected void createDefaultLabelList() { + this.saveLabelType(new AssigneeLabelType()); + this.saveLabelType(new UseCaseLabelType()); + this.saveLabelType(new RatingLabelType()); + this.saveLabelType(new StatusLabelType()); + + AbstractStorageLabelType ratingLabelType = this.getLabelTypes(RatingLabelType.class).get(0); + List> ratingLabelList = this.getAllLabelsForType(ratingLabelType); + if (ratingLabelList.isEmpty()) { + // add default rating labels + this.saveLabel(new StringStorageLabel("Very Bad", ratingLabelType)); + this.saveLabel(new StringStorageLabel("Bad", ratingLabelType)); + this.saveLabel(new StringStorageLabel("Medium", ratingLabelType)); + this.saveLabel(new StringStorageLabel("Good", ratingLabelType)); + this.saveLabel(new StringStorageLabel("Very Good", ratingLabelType)); + } + + AbstractStorageLabelType statusLabelType = this.getLabelTypes(StatusLabelType.class).get(0); + List> statusLabelList = this.getAllLabelsForType(statusLabelType); + if (statusLabelList.isEmpty()) { + // add default status labels + this.saveLabel(new StringStorageLabel("Awaiting Review", statusLabelType)); + this.saveLabel(new StringStorageLabel("In-Progress", statusLabelType)); + this.saveLabel(new StringStorageLabel("Closed", statusLabelType)); + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/dao/impl/TimerDataAggregator.java b/CMR/src/info/novatec/inspectit/cmr/dao/impl/TimerDataAggregator.java new file mode 100644 index 000000000..dc8595864 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/dao/impl/TimerDataAggregator.java @@ -0,0 +1,310 @@ +package info.novatec.inspectit.cmr.dao.impl; + +import info.novatec.inspectit.communication.data.DatabaseAggregatedTimerData; +import info.novatec.inspectit.communication.data.TimerData; + +import java.sql.Timestamp; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; + +import javax.annotation.PostConstruct; + +import org.hibernate.SessionFactory; +import org.hibernate.StatelessSession; +import org.hibernate.Transaction; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.orm.hibernate3.support.HibernateDaoSupport; +import org.springframework.stereotype.Repository; + +/** + * Aggregator for the {@link TimerData} objects that need to be persisted to the DB. + *

+ * The class is marked as final because in the constructor it starts a thread. + * + * @author Ivan Senic + * @see https://confluence.novatec-gmbh.de/display/INSPECTIT/TimerData+Aggregator + * + */ +@Repository +public class TimerDataAggregator extends HibernateDaoSupport { + + /** + * Period of time in which all timer data should be aggregated. In milliseconds. + */ + @Value("${cmr.aggregationPeriod}") + long aggregationPeriod; + + /** + * Max elements in the cache. + */ + @Value("${cmr.maxElements}") + int maxElements; + + /** + * Sleeping period for thread that is cleaning the cache (persisting the objects). In + * milliseconds. + */ + @Value("${cmr.cacheCleanSleepingPeriod}") + long cacheCleanSleepingPeriod; + + /** + * Current element count in cache. + */ + private AtomicInteger elementCount; + + /** + * Map for caching. + */ + private Map map; + + /** + * Queue for knowing the order. + */ + private ConcurrentLinkedQueue queue; + + /** + * List of objects that are out of the cache and need to be persisted. + */ + private ConcurrentLinkedQueue persistList; + + /** + * Lock for persist all. + */ + private ReentrantLock persistAllLock; + + /** + * Pointer to the most recently added {@link TimerData} to the cache. + */ + private volatile TimerData mostRecentlyAdded; + + /** + * This constructor is used to set the {@link SessionFactory} that is needed by + * {@link HibernateDaoSupport}. In a future version it may be useful to go away from the + * {@link HibernateDaoSupport} and directly use the {@link SessionFactory}. This is described + * here: + * http://blog.springsource.com/2007/06/26/so-should-you-still-use-springs-hibernatetemplate + * -andor-jpatemplate + * + * @param sessionFactory + * the hibernate session factory. + */ + @Autowired + public TimerDataAggregator(SessionFactory sessionFactory) { + elementCount = new AtomicInteger(0); + map = new HashMap(); + queue = new ConcurrentLinkedQueue(); + persistList = new ConcurrentLinkedQueue(); + persistAllLock = new ReentrantLock(); + + setSessionFactory(sessionFactory); + } + + /** + * Aggregates the {@link TimerData} object and updates the cache. Note that the given object + * will not be modified by this method. + * + * @param timerData + * {@link TimerData} that holds values to be aggregated. + */ + public void processTimerData(TimerData timerData) { + long aggregationTimestamp = getAlteredTimestamp(timerData); + int cacheHash = getCacheHash(timerData.getPlatformIdent(), timerData.getMethodIdent(), aggregationTimestamp); + + persistAllLock.lock(); + try { + TimerData aggTimerData = map.get(cacheHash); + if (aggTimerData == null) { + // we create a DB aggregated timer data because we don't want to alter objects that + // are in the memory + aggTimerData = new DatabaseAggregatedTimerData(new Timestamp(aggregationTimestamp), timerData.getPlatformIdent(), timerData.getSensorTypeIdent(), timerData.getMethodIdent()); + map.put(cacheHash, aggTimerData); + queue.add(aggTimerData); + mostRecentlyAdded = aggTimerData; + + int count = elementCount.incrementAndGet(); + // remove oldest as long as number of elements is higher than maximum + while (maxElements < count) { + TimerData oldest = queue.poll(); + if (null != oldest) { + map.remove(getCacheHash(oldest.getPlatformIdent(), oldest.getMethodIdent(), oldest.getTimeStamp().getTime())); + persistList.add(oldest); + count = elementCount.decrementAndGet(); + } + } + } + aggTimerData.aggregateTimerData(timerData); + } finally { + persistAllLock.unlock(); + } + } + + /** + * Clears the cache and persists all the data inside. + */ + public void removeAndPersistAll() { + if (!queue.isEmpty()) { + persistAllLock.lock(); + try { + StatelessSession session = getHibernateTemplate().getSessionFactory().openStatelessSession(); + Transaction tx = session.beginTransaction(); + + TimerData oldest = queue.poll(); + while (oldest != null) { + map.remove(getCacheHash(oldest.getPlatformIdent(), oldest.getMethodIdent(), oldest.getTimeStamp().getTime())); + session.insert(oldest); + elementCount.decrementAndGet(); + + oldest = queue.poll(); + } + + tx.commit(); + session.close(); + } finally { + persistAllLock.unlock(); + } + } + } + + /** + * Persists all objects in the persistence list. + */ + void saveAllInPersistList() { + if (!persistList.isEmpty()) { + StatelessSession session = getHibernateTemplate().getSessionFactory().openStatelessSession(); + Transaction tx = session.beginTransaction(); + + TimerData last = persistList.poll(); + while (last != null) { + last.finalizeData(); + session.insert(last); + last = persistList.poll(); + } + + tx.commit(); + session.close(); + } + } + + /** + * Returns the cache hash code. + * + * @param platformIdent + * Platform ident. + * @param methodIdent + * Method ident. + * @param timestampValue + * Time stamp value as long. + * @return Cache hash for the given set of values. + */ + private int getCacheHash(long platformIdent, long methodIdent, long timestampValue) { + final int prime = 31; + int result = 0; + result = prime * result + (int) (platformIdent ^ (platformIdent >>> 32)); + result = prime * result + (int) (methodIdent ^ (methodIdent >>> 32)); + result = prime * result + (int) (timestampValue ^ (timestampValue >>> 32)); + return result; + } + + /** + * Returns the value of the time stamp based on a aggregation period. + * + * @param timerData + * {@link TimerData} to get aggregation time stamp. + * @return Aggregation time stamp. + */ + private long getAlteredTimestamp(TimerData timerData) { + long timestampValue = timerData.getTimeStamp().getTime(); + long newTimestampValue = timestampValue - timestampValue % aggregationPeriod; + return newTimestampValue; + } + + /** + * Starting the thread in post construct, not in constructor. + */ + @PostConstruct + public void postConstruct() { + CacheCleaner cacheCleaner = new CacheCleaner(); + cacheCleaner.start(); + } + + /** + * @return the aggregationPeriod + */ + public long getAggregationPeriod() { + return aggregationPeriod; + } + + /** + * @return the maxElements + */ + public int getMaxElements() { + return maxElements; + } + + /** + * @return the cacheCleanSleepingPeriod + */ + public long getCacheCleanSleepingPeriod() { + return cacheCleanSleepingPeriod; + } + + /** + * Gets {@link #elementCount}. + * + * @return {@link #elementCount} + */ + public int getElementCount() { + return elementCount.get(); + } + + /** + * Cache cleaner, or thread that is constantly checking if there is something to be persisted. + * + * @author Ivan Senic + * + */ + private class CacheCleaner extends Thread { + + /** + * Element that is last checked by thread. If this element is same as most recently added + * element, all elements in the cache will be persisted. + */ + private TimerData lastChecked; + + /** + * Constructor. Set thread as daemon and gives it minimum priority. + */ + public CacheCleaner() { + setName("timer-data-aggregator-cache-cleaner-thread"); + setDaemon(true); + setPriority(MIN_PRIORITY); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + while (true) { + TimerData timerData = mostRecentlyAdded; + if (timerData != null) { + if (timerData.equals(lastChecked)) { + removeAndPersistAll(); + } + lastChecked = timerData; + } + saveAllInPersistList(); + try { + Thread.sleep(cacheCleanSleepingPeriod); + } catch (InterruptedException e) { + Thread.interrupted(); + } + } + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/indexing/impl/RootBranchFactory.java b/CMR/src/info/novatec/inspectit/cmr/indexing/impl/RootBranchFactory.java new file mode 100644 index 000000000..a49d2798d --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/indexing/impl/RootBranchFactory.java @@ -0,0 +1,119 @@ +package info.novatec.inspectit.cmr.indexing.impl; + +import info.novatec.inspectit.cmr.indexing.impl.RootBranchFactory.RootBranch; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.indexing.buffer.IBufferBranchIndexer; +import info.novatec.inspectit.indexing.buffer.IBufferTreeComponent; +import info.novatec.inspectit.indexing.buffer.impl.Branch; +import info.novatec.inspectit.indexing.buffer.impl.BufferBranchIndexer; +import info.novatec.inspectit.indexing.impl.IndexingException; +import info.novatec.inspectit.indexing.indexer.impl.ObjectTypeIndexer; +import info.novatec.inspectit.indexing.indexer.impl.PlatformIdentIndexer; +import info.novatec.inspectit.indexing.indexer.impl.TimestampIndexer; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.stereotype.Component; + +/** + * Factory that creates the root branch for indexing tree. This root branch will be injected in + * Spring as a bean. + * + * @author Ivan Senic + * + */ +@Component +public class RootBranchFactory implements FactoryBean> { + + /** + * {@inheritDoc} + */ + @Override + public RootBranch getObject() throws Exception { + BufferBranchIndexer timestampIndexer = new BufferBranchIndexer(new TimestampIndexer()); + BufferBranchIndexer objectTypeIndexer = new BufferBranchIndexer(new ObjectTypeIndexer(), timestampIndexer); + BufferBranchIndexer platformIndexer = new BufferBranchIndexer(new PlatformIdentIndexer(), objectTypeIndexer); + return new RootBranch(platformIndexer); + } + + /** + * {@inheritDoc} + */ + @Override + public Class getObjectType() { + return IBufferTreeComponent.class; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isSingleton() { + return true; + } + + /** + * Root branch. It has additional functionality of generating IDs for the elements that need to + * be put into the indexing tree. + * + * @author Ivan Senic + * + */ + public static class RootBranch extends Branch { + + /** + * Runnable for cutting the empty tree components. + */ + private Runnable clearEmptyComponentsRunnable = new Runnable() { + + @Override + public void run() { + RootBranch.this.clearEmptyComponents(); + } + }; + + /** + * Future that holds state of clear empty components runnable. + */ + private Future clearEmptyComponentsFuture; + + /** + * Default constructor. + * + * @param branchIndexer + * Branch indexer for root branch. + */ + public RootBranch(IBufferBranchIndexer branchIndexer) { + super(branchIndexer); + } + + /** + * {@inheritDoc} + *

+ * This method also sets the ID of the element that is put into the indexing tree. + */ + @Override + public E put(E element) throws IndexingException { + if (null == element) { + throw new IndexingException("Null object can not be indexed."); + } + return super.put(element); + } + + /** + * {@inheritDoc} + */ + @Override + public void cleanWithRunnable(ExecutorService executorService) { + super.cleanWithRunnable(executorService); + if (clearEmptyComponentsFuture == null || clearEmptyComponentsFuture.isDone()) { + // Submit runnable only if the future is signaling that the last one was done. + clearEmptyComponentsFuture = executorService.submit(clearEmptyComponentsRunnable); + } + } + + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/jaxb/JAXBTransformator.java b/CMR/src/info/novatec/inspectit/cmr/jaxb/JAXBTransformator.java new file mode 100644 index 000000000..e42359726 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/jaxb/JAXBTransformator.java @@ -0,0 +1,103 @@ +package info.novatec.inspectit.cmr.jaxb; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + +import org.xml.sax.SAXException; + +/** + * Simple component for marshall and unmarshall operation on JAXB. + * + * @author Ivan Senic + * + */ +public class JAXBTransformator { + + /** + * Marshals the object to the given path that must represent a path to the file. + * + * @param path + * Path to file + * @param object + * Object to marshal + * @param noNamespaceSchemaLocation + * NoNamespaceSchemaLocation to set. If it's null no location will be + * set. + * @throws JAXBException + * If {@link JAXBException} occurs. + * @throws IOException + * If {@link IOException} occurs. + */ + public void marshall(Path path, Object object, String noNamespaceSchemaLocation) throws JAXBException, IOException { + if (Files.isDirectory(path)) { + throw new IOException("Can not marshal object to the path that represents the directory"); + } + Files.deleteIfExists(path); + + JAXBContext context = JAXBContext.newInstance(object.getClass()); + Marshaller marshaller = context.createMarshaller(); + if (null != noNamespaceSchemaLocation) { + marshaller.setProperty(Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION, noNamespaceSchemaLocation); + } + + try (OutputStream outputStream = Files.newOutputStream(path, StandardOpenOption.CREATE_NEW)) { + marshaller.marshal(object, outputStream); + } + } + + /** + * Unmarshalls the given file. The root class of the XML must be given. + * + * @param + * Type of root object. + * @param path + * Path to file to unmarshall. + * @param schemaPath + * Path to the XSD schema that will be used to validate the XML file. If no schema is + * provided no validation will be performed. + * @param rootClass + * Root class of the XML document. + * @return Unmarshalled object. + * @throws JAXBException + * If {@link JAXBException} occurs during loading. + * @throws IOException + * If {@link IOException} occurs during loading. + * @throws SAXException + * If {@link SAXException} occurs during schema parsing. + */ + @SuppressWarnings("unchecked") + public T unmarshall(Path path, Path schemaPath, Class rootClass) throws JAXBException, IOException, SAXException { + if (Files.notExists(path) || Files.isDirectory(path)) { + return null; + } + + JAXBContext context = JAXBContext.newInstance(rootClass); + Unmarshaller unmarshaller = context.createUnmarshaller(); + + if (null != schemaPath && Files.exists(schemaPath)) { + SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + try (InputStream inputStream = Files.newInputStream(schemaPath, StandardOpenOption.READ)) { + Schema schema = sf.newSchema(new StreamSource(inputStream)); + unmarshaller.setSchema(schema); + } + } + + try (InputStream inputStream = Files.newInputStream(path, StandardOpenOption.READ)) { + return (T) unmarshaller.unmarshal(inputStream); + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/jetty/FileUploadServlet.java b/CMR/src/info/novatec/inspectit/cmr/jetty/FileUploadServlet.java new file mode 100644 index 000000000..f1940250d --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/jetty/FileUploadServlet.java @@ -0,0 +1,111 @@ +package info.novatec.inspectit.cmr.jetty; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Enumeration; +import java.util.List; +import java.util.Objects; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.InitializingBean; + +/** + * A simple file upload servlet that depends on the the org.mortbay.servlet.MultiPartFilter. The + * filter should prepare the files that are sent in the "multipart/form-data" encoding as a list of + * files in the request attribute {@value #MULTI_PART_FILTER_FILES}. + *

+ * This servlet can be used for uploading any number files in one request. The file has to be + * uploaded with the name that represents the relative path to the upload folder where file will be + * saved. + * + * @author Ivan Senic + * + */ +public class FileUploadServlet extends HttpServlet implements InitializingBean { + + /** + * Generated UID. + */ + private static final long serialVersionUID = 5619516365594064035L; + + /** + * ID of the HTTP request attribute that holds the uploaded files. The attribute will be set by + * the org.mortbay.servlet.MultiPartFilter. + */ + private static final String MULTI_PART_FILTER_FILES = "org.mortbay.servlet.MultiPartFilter.files"; + + /** + * Directory where the files will be stored. + */ + private String directoryToStore; + + /** + * {@inheritDoc} + */ + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + List files = (List) req.getAttribute(MULTI_PART_FILTER_FILES); + + if (null != files) { + for (int i = 0; i < files.size(); i++) { + File file = (File) files.get(i); + StringBuilder nameBuffer = new StringBuilder(); + nameBuffer.append(directoryToStore); + nameBuffer.append(File.separatorChar); + + Enumeration attributeNames = req.getAttributeNames(); + while (attributeNames.hasMoreElements()) { + String fileName = attributeNames.nextElement(); + if (Objects.equals(file, req.getAttribute(fileName))) { + nameBuffer.append(fileName); + break; + } + } + + File outputFile = new File(nameBuffer.toString()); + if (outputFile.exists()) { + throw new IOException("Upload file already exists. Aborting the upload."); + } + File outputDir = outputFile.getParentFile(); + if (null != outputDir && !outputDir.exists()) { + if (!outputDir.mkdirs()) { + throw new IOException("Needed directory " + outputDir + " can not be created."); + } + } + if (!file.renameTo(outputFile)) { + throw new IOException("Temporary file " + file + " can not be renamed to the correct upload file name " + outputFile + "."); + } + } + } + } + + /** + * {@inheritDoc} + */ + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + doGet(req, resp); + } + + /** + * Sets {@link #directoryToStore}. + * + * @param directoryToStore + * New value for {@link #directoryToStore} + */ + public void setDirectoryToStore(String directoryToStore) { + this.directoryToStore = directoryToStore; + } + + /** + * {@inheritDoc} + */ + @Override + public void afterPropertiesSet() throws Exception { + Files.createDirectories(Paths.get(directoryToStore)); + } +} diff --git a/CMR/src/info/novatec/inspectit/cmr/jetty/JettyWebApplicationContextInitializer.java b/CMR/src/info/novatec/inspectit/cmr/jetty/JettyWebApplicationContextInitializer.java new file mode 100644 index 000000000..82002c833 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/jetty/JettyWebApplicationContextInitializer.java @@ -0,0 +1,99 @@ +package info.novatec.inspectit.cmr.jetty; + +import info.novatec.inspectit.spring.logger.Log; + +import javax.annotation.PostConstruct; +import javax.servlet.ServletContext; + +import org.mortbay.jetty.servlet.Context; +import org.slf4j.Logger; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.web.context.support.GenericWebApplicationContext; + +/** + * This class binds an empty Spring {@link GenericWebApplicationContext} to the + * ServletContext of a given {@link Context}. + * + * The newly created web application context is required, so that servlets managed via Jetty can + * attach to it. While the context itself is usually empty, it is bound to the parent context + * provided through {@link ApplicationContextAware#setApplicationContext(ApplicationContext)}. Thus, + * all Jetty managed servlets may obtain the web application context through the configured + * {@link #setContextAttribute(String) context attribute}. + * + * The configured {@link #setJettyContext(Context) Jetty context} is automatically started upon + * {@link #initialize() initialization}. + * + * @author NovaProvisioning + */ +public class JettyWebApplicationContextInitializer implements ApplicationContextAware { + + /** The logger of this class. */ + @Log + Logger log; + + /** + * The context attribute. + */ + private String contextAttribute; + + /** + * The jetty context being inject. + */ + private Context jettyContext; + + /** + * The real application context. + */ + private ApplicationContext ctx; + + /** + * Initializes the context attribute used to bind the web application context to the + * ServletContext. + * + * @param contextAttribute + * identifier to be used for binding + */ + public void setContextAttribute(String contextAttribute) { + this.contextAttribute = contextAttribute; + } + + /** + * Injects the Jetty context object to be initialized. + * + * @param jettyContext + * the context + */ + public void setJettyContext(Context jettyContext) { + this.jettyContext = jettyContext; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + this.ctx = applicationContext; + } + + /** + * Performs initialization of the web context binding it to Jetty. + * + * @throws Exception + * in case of an error while starting the Jetty context + */ + @PostConstruct + public void postConstruct() throws Exception { + ServletContext servletContext = jettyContext.getServletContext(); + + GenericWebApplicationContext webCtx = new GenericWebApplicationContext(); + webCtx.setServletContext(servletContext); + webCtx.setParent(ctx); + webCtx.refresh(); + + servletContext.setAttribute(contextAttribute, webCtx); + jettyContext.start(); + + if (log.isInfoEnabled()) { + log.info("| Jetty Web Application Context started!"); + } + } + +} \ No newline at end of file diff --git a/CMR/src/info/novatec/inspectit/cmr/processor/AbstractChainedCmrDataProcessor.java b/CMR/src/info/novatec/inspectit/cmr/processor/AbstractChainedCmrDataProcessor.java new file mode 100644 index 000000000..7fe5a0e8e --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/processor/AbstractChainedCmrDataProcessor.java @@ -0,0 +1,75 @@ +package info.novatec.inspectit.cmr.processor; + +import info.novatec.inspectit.communication.DefaultData; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.StatelessSession; + +/** + * Abstract data processor that passes data to chained processors. + * + * @author Ivan Senic + * + */ +public abstract class AbstractChainedCmrDataProcessor extends AbstractCmrDataProcessor { + + /** + * List of chained processors. + */ + private List dataProcessors; + + /** + * Default constructor. + */ + public AbstractChainedCmrDataProcessor() { + dataProcessors = new ArrayList(); + } + + /** + * Secondary constructor. + * + * @param dataProcessors + * List of chained processors. + */ + public AbstractChainedCmrDataProcessor(List dataProcessors) { + this.dataProcessors = dataProcessors; + } + + /** + * Should the data be passed to the chained processors. + * + * @param defaultData + * {@link DefaultData}. + * @return True if it should be passed, false otherwise. + */ + protected abstract boolean shouldBePassedToChainedProcessors(DefaultData defaultData); + + /** + * {@inheritDoc} + */ + @Override + protected void processData(DefaultData defaultData, StatelessSession session) { + if (shouldBePassedToChainedProcessors(defaultData)) { + passToChainedProcessors(defaultData, session); + } + } + + /** + * Passed the default data to all chained processors. + * + * @param defaultData + * Data to pass. + * @param session + * {@link StatelessSession} to save data in DB if needed. + */ + protected void passToChainedProcessors(DefaultData defaultData, StatelessSession session) { + if (null != dataProcessors) { + for (AbstractCmrDataProcessor dataProcessor : dataProcessors) { + dataProcessor.process(defaultData, session); + } + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/processor/AbstractCmrDataProcessor.java b/CMR/src/info/novatec/inspectit/cmr/processor/AbstractCmrDataProcessor.java new file mode 100644 index 000000000..083cddf35 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/processor/AbstractCmrDataProcessor.java @@ -0,0 +1,67 @@ +package info.novatec.inspectit.cmr.processor; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.storage.processor.AbstractDataProcessor; + +import java.util.Collection; + +import org.hibernate.StatelessSession; + +/** + * Abstract processor class for CMR data. + * + * @author Ivan Senic + * + */ +public abstract class AbstractCmrDataProcessor { + + /** + * Processes many {@link DefaultData} objects. + * + * @param defaultDatas + * Default data objects. + * @param session + * {@link StatelessSession} to save data in DB if needed. + */ + public void process(Collection defaultDatas, StatelessSession session) { + for (DefaultData defaultData : defaultDatas) { + process(defaultData, session); + } + } + + /** + * Processes one {@link DefaultData} object. This method will check is + * {@link #canBeProcessed(DefaultData)} is true, and then delegate the processing to the + * {@link #processData(DefaultData)} method. + * + * @param defaultData + * Default data object. + * @param session + * {@link StatelessSession} to save data in DB if needed. + */ + public void process(DefaultData defaultData, StatelessSession session) { + if (canBeProcessed(defaultData)) { + processData(defaultData, session); + } + } + + /** + * Concrete method for processing. Implemented by sub-classes. + * + * @param defaultData + * Default data object. + * @param session + * {@link StatelessSession} to save data in DB if needed. + */ + protected abstract void processData(DefaultData defaultData, StatelessSession session); + + /** + * Returns if the {@link DefaultData} object can be processed by this + * {@link AbstractDataProcessor}. + * + * @param defaultData + * Default data object. + * @return True if data can be processed, false otherwise. + */ + public abstract boolean canBeProcessed(DefaultData defaultData); +} diff --git a/CMR/src/info/novatec/inspectit/cmr/processor/impl/BufferInserterCmrProcessor.java b/CMR/src/info/novatec/inspectit/cmr/processor/impl/BufferInserterCmrProcessor.java new file mode 100644 index 000000000..3ec25b42a --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/processor/impl/BufferInserterCmrProcessor.java @@ -0,0 +1,61 @@ +package info.novatec.inspectit.cmr.processor.impl; + +import info.novatec.inspectit.cmr.cache.IBuffer; +import info.novatec.inspectit.cmr.cache.impl.BufferElement; +import info.novatec.inspectit.cmr.processor.AbstractCmrDataProcessor; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.communication.data.InvocationAwareData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; + +import org.hibernate.StatelessSession; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Buffer inserter data processor. Inserts only {@link MethodSensorData} data objects that are not + * part of an invocation. + * + * @author Ivan Senic + * + */ +public class BufferInserterCmrProcessor extends AbstractCmrDataProcessor { + + /** + * Buffer to inser elements to. + */ + @Autowired + IBuffer buffer; + + /** + * {@inheritDoc} + */ + @Override + protected void processData(DefaultData defaultData, StatelessSession session) { + buffer.put(new BufferElement((MethodSensorData) defaultData)); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canBeProcessed(DefaultData defaultData) { + if (null == defaultData) { + return false; + } else if (!(defaultData instanceof MethodSensorData)) { + // we only put to buffer method sensor data + return false; + } else if (defaultData instanceof InvocationAwareData) { + // we don't put to buffer elements that are inside of invocation + if (!((InvocationAwareData) defaultData).isOnlyFoundOutsideInvocations()) { + return false; + } + } else if (defaultData instanceof InvocationSequenceData) { + // we don't put to buffer invocations that are not root + if (((InvocationSequenceData) defaultData).getParentSequence() != null) { + return false; + } + } + return true; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/processor/impl/CacheIdGeneratorCmrProcessor.java b/CMR/src/info/novatec/inspectit/cmr/processor/impl/CacheIdGeneratorCmrProcessor.java new file mode 100644 index 000000000..708b53cf0 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/processor/impl/CacheIdGeneratorCmrProcessor.java @@ -0,0 +1,41 @@ +package info.novatec.inspectit.cmr.processor.impl; + +import info.novatec.inspectit.cmr.processor.AbstractCmrDataProcessor; +import info.novatec.inspectit.cmr.util.CacheIdGenerator; +import info.novatec.inspectit.communication.DefaultData; + +import org.hibernate.StatelessSession; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Simple {@link AbstractCmrDataProcessor} that can assign the ID for the {@link DefaultData} using + * {@link CacheIdGenerator}. + * + * @author Ivan Senic + * + */ +public class CacheIdGeneratorCmrProcessor extends AbstractCmrDataProcessor { + + /** + * {@link CacheIdGenerator}. + */ + @Autowired + CacheIdGenerator cacheIdGenerator; + + /** + * {@inheritDoc} + */ + @Override + protected void processData(DefaultData defaultData, StatelessSession session) { + cacheIdGenerator.assignObjectAnId(defaultData); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canBeProcessed(DefaultData defaultData) { + return null != defaultData; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/processor/impl/ExceptionMessageCmrProcessor.java b/CMR/src/info/novatec/inspectit/cmr/processor/impl/ExceptionMessageCmrProcessor.java new file mode 100644 index 000000000..c56dd0879 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/processor/impl/ExceptionMessageCmrProcessor.java @@ -0,0 +1,47 @@ +package info.novatec.inspectit.cmr.processor.impl; + +import info.novatec.inspectit.cmr.processor.AbstractCmrDataProcessor; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; + +import org.hibernate.StatelessSession; + +/** + * Processor that connects error messages in the {@link ExceptionSensorData}. + * + * @author Ivan Senic + * + */ +public class ExceptionMessageCmrProcessor extends AbstractCmrDataProcessor { + + /** + * {@inheritDoc} + */ + @Override + protected void processData(DefaultData defaultData, StatelessSession session) { + connectErrorMessagesInExceptionData((ExceptionSensorData) defaultData); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canBeProcessed(DefaultData defaultData) { + return defaultData instanceof ExceptionSensorData; + } + + /** + * Connects exception message between linked exception data. + * + * @param exceptionSensorData + * Parent exception data, thus the one that has exception event CREATED. + */ + private void connectErrorMessagesInExceptionData(ExceptionSensorData exceptionSensorData) { + ExceptionSensorData child = exceptionSensorData.getChild(); + if (null != child) { + child.setErrorMessage(exceptionSensorData.getErrorMessage()); + connectErrorMessagesInExceptionData(child); + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/processor/impl/IndexerCmrProcessor.java b/CMR/src/info/novatec/inspectit/cmr/processor/impl/IndexerCmrProcessor.java new file mode 100644 index 000000000..55c4d9c6b --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/processor/impl/IndexerCmrProcessor.java @@ -0,0 +1,59 @@ +package info.novatec.inspectit.cmr.processor.impl; + +import info.novatec.inspectit.cmr.processor.AbstractCmrDataProcessor; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.InvocationAwareData; +import info.novatec.inspectit.indexing.buffer.IBufferTreeComponent; +import info.novatec.inspectit.indexing.impl.IndexingException; +import info.novatec.inspectit.spring.logger.Log; + +import org.hibernate.StatelessSession; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Processor that index elements directly to the indexing tree. + * + * @author Ivan Senic + * + */ +public class IndexerCmrProcessor extends AbstractCmrDataProcessor { + + /** + * The logger of this class. + */ + @Log + Logger log; + + /** + * The indexing tree for direct object indexing. + */ + @Autowired + IBufferTreeComponent indexingTree; + + /** + * {@inheritDoc} + */ + @Override + protected void processData(DefaultData defaultData, StatelessSession session) { + try { + indexingTree.put(defaultData); + } catch (IndexingException e) { + // should never happen + log.error(e.getMessage(), e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canBeProcessed(DefaultData defaultData) { + if (null != defaultData) { + // only directly index the invocation aware data that is in invocation + return defaultData instanceof InvocationAwareData && ((InvocationAwareData) defaultData).isOnlyFoundInInvocations(); + } + return false; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/processor/impl/InvocationModifierCmrProcessor.java b/CMR/src/info/novatec/inspectit/cmr/processor/impl/InvocationModifierCmrProcessor.java new file mode 100644 index 000000000..8cd0e71e2 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/processor/impl/InvocationModifierCmrProcessor.java @@ -0,0 +1,189 @@ +package info.novatec.inspectit.cmr.processor.impl; + +import info.novatec.inspectit.cmr.processor.AbstractChainedCmrDataProcessor; +import info.novatec.inspectit.cmr.processor.AbstractCmrDataProcessor; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.ExceptionEvent; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.InvocationSequenceDataHelper; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; + +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; +import org.hibernate.StatelessSession; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Processor performing necessary calculation and fixes. This is special type of chained processor + * that does not pass the incoming object to the chained processors, but might do so with some other + * objects. + * + * @author Ivan Senic + * + */ +public class InvocationModifierCmrProcessor extends AbstractChainedCmrDataProcessor { + + /** + * Message processor for exception that we need to call directly. It's because we need to do + * that for all exceptions, but we will send only one to the chained processors, cause in the + * chain there will be indexed and stuff and we don’t want that for all exceptions, but only + * that survive constructor delegation. + */ + @Autowired + ExceptionMessageCmrProcessor exceptionMessageCmrProcessor; + + /** + * Default constructor. + * + * @param dataProcessors + * Chained processors. + */ + public InvocationModifierCmrProcessor(List dataProcessors) { + super(dataProcessors); + } + + /** + * {@inheritDoc} + */ + protected void processData(DefaultData defaultData, StatelessSession session) { + InvocationSequenceData invocation = (InvocationSequenceData) defaultData; + extractDataFromInvocation(session, invocation, invocation); + } + + /** + * {@inheritDoc} + */ + @Override + protected boolean shouldBePassedToChainedProcessors(DefaultData defaultData) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canBeProcessed(DefaultData defaultData) { + return defaultData instanceof InvocationSequenceData; + } + + /** + * Extract data from the invocation in the way that timer data is saved to the Db, while SQL + * statements and Exceptions are indexed into the root branch. + * + * @param session + * Session needed for DB persistence. + * @param invData + * Invocation data to be extracted. + * @param topInvocationParent + * Top invocation object. + * + */ + private void extractDataFromInvocation(StatelessSession session, InvocationSequenceData invData, InvocationSequenceData topInvocationParent) { + double exclusiveDurationDelta = 0d; + + for (InvocationSequenceData child : (List) invData.getNestedSequences()) { + // pass child to chained processors + passToChainedProcessors(child, session); + + // include times from timer, sql or invocation itself + if (null != child.getTimerData()) { + exclusiveDurationDelta += child.getTimerData().getDuration(); + } else if (null != child.getSqlStatementData()) { + // I don't know if the situation that both timer and sql are set in one + // invocation, but just to be sure I only include the time of the sql, if i did + // not already included the time of the timer before + exclusiveDurationDelta += child.getSqlStatementData().getDuration(); + } else { + exclusiveDurationDelta += InvocationSequenceDataHelper.computeNestedDuration(child); + } + + // go to the recursion + extractDataFromInvocation(session, child, topInvocationParent); + } + + // process the SQL Statement and Timer + processSqlStatementData(session, invData, topInvocationParent); + processTimerData(session, invData, topInvocationParent, exclusiveDurationDelta); + processExceptionSensorData(session, invData, topInvocationParent); + } + + /** + * Process SQL statement if one exists in the invData object and passes it to the chained + * processors. + * + * @param session + * Session needed for DB persistence. + * @param invData + * Invocation data to be processed. + * @param topInvocationParent + * Top invocation object. + */ + private void processSqlStatementData(StatelessSession session, InvocationSequenceData invData, InvocationSequenceData topInvocationParent) { + SqlStatementData sqlStatementData = invData.getSqlStatementData(); + if (null != sqlStatementData) { + topInvocationParent.setNestedSqlStatements(Boolean.TRUE); + sqlStatementData.addInvocationParentId(topInvocationParent.getId()); + passToChainedProcessors(sqlStatementData, session); + } + } + + /** + * Process timer data if one exists in the invData object and passes it to the chained + * processors. + * + * @param session + * Session needed for DB persistence. + * @param invData + * Invocation data to be processed. + * @param topInvocationParent + * Top invocation object. + * @param exclusiveDurationDelta + * Duration to subtract from timer duration to get the exclusive duration. + */ + private void processTimerData(StatelessSession session, InvocationSequenceData invData, InvocationSequenceData topInvocationParent, double exclusiveDurationDelta) { + TimerData timerData = invData.getTimerData(); + if (null != timerData) { + double exclusiveTime = invData.getTimerData().getDuration() - exclusiveDurationDelta; + timerData.setExclusiveCount(1L); + timerData.setExclusiveDuration(exclusiveTime); + timerData.calculateExclusiveMax(exclusiveTime); + timerData.calculateExclusiveMin(exclusiveTime); + timerData.addInvocationParentId(topInvocationParent.getId()); + passToChainedProcessors(invData.getTimerData(), session); + } + } + + /** + * Process all the exceptions in the invData and passes exceptions to the chained processors.
+ *
+ * Note also that only exception data with CREATED event are processed, since the PASSED and + * HANDLED should be connected as children to the CREATED one. + * + * @param session + * Session needed for DB persistence. + * @param invData + * Invocation data to be processed. + * @param topInvocationParent + * Top invocation object. + */ + private void processExceptionSensorData(StatelessSession session, InvocationSequenceData invData, InvocationSequenceData topInvocationParent) { + if (CollectionUtils.isNotEmpty(invData.getExceptionSensorDataObjects())) { + for (ExceptionSensorData exceptionData : invData.getExceptionSensorDataObjects()) { + if (exceptionData.getExceptionEvent() == ExceptionEvent.CREATED) { + // only if created exception is in invocation set to the parent + topInvocationParent.setNestedExceptions(Boolean.TRUE); + + // we need to directly call Exception message processor, cause it can not be + // chained + exceptionMessageCmrProcessor.process(exceptionData, session); + exceptionData.addInvocationParentId(topInvocationParent.getId()); + passToChainedProcessors(exceptionData, session); + } + } + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/processor/impl/RecorderCmrProcessor.java b/CMR/src/info/novatec/inspectit/cmr/processor/impl/RecorderCmrProcessor.java new file mode 100644 index 000000000..4ae57fda2 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/processor/impl/RecorderCmrProcessor.java @@ -0,0 +1,44 @@ +package info.novatec.inspectit.cmr.processor.impl; + +import info.novatec.inspectit.cmr.processor.AbstractCmrDataProcessor; +import info.novatec.inspectit.cmr.storage.CmrStorageManager; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.storage.recording.RecordingState; + +import org.hibernate.StatelessSession; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Simple {@link AbstractCmrDataProcessor} that passes data to be recorded if recording is "ON" on + * the CMR. + * + * @author Ivan Senic + * + */ +public class RecorderCmrProcessor extends AbstractCmrDataProcessor { + + /** + * {@link CmrStorageManager}. + */ + @Autowired + CmrStorageManager storageManager; + + /** + * {@inheritDoc} + */ + @Override + protected void processData(DefaultData defaultData, StatelessSession session) { + if (storageManager.getRecordingState() == RecordingState.ON) { + storageManager.record(defaultData); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canBeProcessed(DefaultData defaultData) { + return null != defaultData; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/processor/impl/SessionInserterCmrProcessor.java b/CMR/src/info/novatec/inspectit/cmr/processor/impl/SessionInserterCmrProcessor.java new file mode 100644 index 000000000..c16687f52 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/processor/impl/SessionInserterCmrProcessor.java @@ -0,0 +1,56 @@ +package info.novatec.inspectit.cmr.processor.impl; + +import info.novatec.inspectit.cmr.processor.AbstractCmrDataProcessor; +import info.novatec.inspectit.communication.DefaultData; + +import java.util.Collections; +import java.util.List; + +import org.hibernate.StatelessSession; + +/** + * Processor that saves objects to database via {@link StatelessSession}. + * + * @author Ivan Senic + * + */ +public class SessionInserterCmrProcessor extends AbstractCmrDataProcessor { + + /** + * List of classes that should be saved by this simple saver. + */ + private List> classes; + + /** + * Default constructor. + * + * @param classes + * List of classes that should be saved by this simple saver. + */ + public SessionInserterCmrProcessor(List> classes) { + this.classes = classes; + if (null == this.classes) { + this.classes = Collections.emptyList(); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void processData(DefaultData defaultData, StatelessSession session) { + session.insert(defaultData); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canBeProcessed(DefaultData defaultData) { + if (null != defaultData) { + return classes.contains(defaultData.getClass()); + } + return false; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/processor/impl/SqlExclusiveTimeCmrProcessor.java b/CMR/src/info/novatec/inspectit/cmr/processor/impl/SqlExclusiveTimeCmrProcessor.java new file mode 100644 index 000000000..d50d8db5d --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/processor/impl/SqlExclusiveTimeCmrProcessor.java @@ -0,0 +1,38 @@ +package info.novatec.inspectit.cmr.processor.impl; + +import info.novatec.inspectit.cmr.processor.AbstractCmrDataProcessor; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.SqlStatementData; + +import org.hibernate.StatelessSession; + +/** + * Processor that sets the correct exclusive time for {@link SqlStatementData} because it's always + * known. + * + * @author Ivan Senic + * + */ +public class SqlExclusiveTimeCmrProcessor extends AbstractCmrDataProcessor { + + /** + * {@inheritDoc} + */ + @Override + protected void processData(DefaultData defaultData, StatelessSession session) { + SqlStatementData sqlStatementData = (SqlStatementData) defaultData; + sqlStatementData.setExclusiveCount(1L); + sqlStatementData.setExclusiveDuration(sqlStatementData.getDuration()); + sqlStatementData.calculateExclusiveMax(sqlStatementData.getDuration()); + sqlStatementData.calculateExclusiveMin(sqlStatementData.getDuration()); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canBeProcessed(DefaultData defaultData) { + return defaultData instanceof SqlStatementData; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/processor/impl/SystemInformationDataCmrProcessor.java b/CMR/src/info/novatec/inspectit/cmr/processor/impl/SystemInformationDataCmrProcessor.java new file mode 100644 index 000000000..5916b8a6e --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/processor/impl/SystemInformationDataCmrProcessor.java @@ -0,0 +1,42 @@ +package info.novatec.inspectit.cmr.processor.impl; + +import info.novatec.inspectit.cmr.processor.AbstractCmrDataProcessor; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.SystemInformationData; +import info.novatec.inspectit.communication.data.VmArgumentData; + +import java.util.Set; + +import org.hibernate.StatelessSession; + +/** + * Processor that correctly saves the {@link SystemInformationData} to the database. + * + * @author Ivan Senic + * + */ +public class SystemInformationDataCmrProcessor extends AbstractCmrDataProcessor { + + /** + * {@inheritDoc} + */ + @Override + protected void processData(DefaultData defaultData, StatelessSession session) { + SystemInformationData info = (SystemInformationData) defaultData; + long systemInformationId = ((Long) session.insert(info)).longValue(); + Set vmSet = info.getVmSet(); + for (VmArgumentData argumentData : vmSet) { + argumentData.setSystemInformationId(systemInformationId); + session.insert(argumentData); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canBeProcessed(DefaultData defaultData) { + return defaultData instanceof SystemInformationData; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/processor/impl/TimerDataChartingCmrProcessor.java b/CMR/src/info/novatec/inspectit/cmr/processor/impl/TimerDataChartingCmrProcessor.java new file mode 100644 index 000000000..fef357385 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/processor/impl/TimerDataChartingCmrProcessor.java @@ -0,0 +1,50 @@ +package info.novatec.inspectit.cmr.processor.impl; + +import info.novatec.inspectit.cmr.dao.impl.TimerDataAggregator; +import info.novatec.inspectit.cmr.processor.AbstractCmrDataProcessor; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.TimerData; + +import org.hibernate.StatelessSession; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Processor that saves {@link TimerData} or {@link HttpTimerData} to database correctly if the + * charting is on. + * + * @author Ivan Senic + * + */ +public class TimerDataChartingCmrProcessor extends AbstractCmrDataProcessor { + + /** + * {@link TimerDataAggregator} for {@link TimerData} aggregation. + */ + @Autowired + TimerDataAggregator timerDataAggregator; + + /** + * {@inheritDoc} + */ + @Override + protected void processData(DefaultData defaultData, StatelessSession session) { + if (defaultData instanceof HttpTimerData) { + long bufferId = defaultData.getId(); + defaultData.setId(0); + session.insert(defaultData); + defaultData.setId(bufferId); + } else { + timerDataAggregator.processTimerData((TimerData) defaultData); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canBeProcessed(DefaultData defaultData) { + return defaultData instanceof TimerData && ((TimerData) defaultData).isCharting(); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/property/PropertyManager.java b/CMR/src/info/novatec/inspectit/cmr/property/PropertyManager.java new file mode 100644 index 000000000..bd3797244 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/property/PropertyManager.java @@ -0,0 +1,350 @@ +package info.novatec.inspectit.cmr.property; + +import info.novatec.inspectit.cmr.jaxb.JAXBTransformator; +import info.novatec.inspectit.cmr.property.configuration.AbstractProperty; +import info.novatec.inspectit.cmr.property.configuration.Configuration; +import info.novatec.inspectit.cmr.property.configuration.PropertySection; +import info.novatec.inspectit.cmr.property.configuration.SingleProperty; +import info.novatec.inspectit.cmr.property.configuration.validation.PropertyValidation; +import info.novatec.inspectit.cmr.property.update.AbstractPropertyUpdate; +import info.novatec.inspectit.cmr.property.update.IPropertyUpdate; +import info.novatec.inspectit.cmr.property.update.configuration.ConfigurationUpdate; +import info.novatec.inspectit.cmr.util.ShutdownService; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; + +import javax.xml.bind.JAXBException; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.xml.sax.SAXException; + +/** + * Properties manager bean that controls all properties specified in the configuration files and + * provides the {@link Properties} object as a bean for the Spring context property-placeholder. + * + * @author Ivan Senic + * + */ +@org.springframework.context.annotation.Configuration +public class PropertyManager { + + /** + * The logger of this class. + *

+ * Must be declared manually since loading of the properties is done before beans + * post-processing. + */ + private static final Logger LOG = LoggerFactory.getLogger(PropertyManager.class); + + /** + * Name of the local properties bean that will be created. + */ + public static final String LOCAL_PROPERTIES_BEAN_NAME = "localPropertiesBean"; + + /** + * Directory where configuration files are places. + */ + private static final String CONFIG_DIR = "config"; + + /** + * Directory where configuration files are places. + */ + private static final String SCHEMA_DIR = CONFIG_DIR + File.separatorChar + "schema"; + + /** + * Name of the schema file for configuration. + */ + private static final String CONFIGURATION_SCHEMA_FILE = "configurationSchema.xsd"; + + /** + * Name of the schema file for configuration update. + */ + private static final String CONFIGURATION_UPDATE_SCHEMA_FILE = "configurationUpdateSchema.xsd"; + + /** + * File name where default configuration is stored. + */ + private static final String DEFAULT_CONFIG_FILE = "default.xml"; + + /** + * File name where the current configuration updates are stored. + */ + private static final String CONFIG_UPDATE_FILE = "configurationUpdates.xml"; + + /** + * Default configuration. + */ + private Configuration configuration; + + /** + * Currently used configuration update. + */ + private ConfigurationUpdate configurationUpdate; + + /** + * {@link PropertyUpdateExecutor} that executes methods need to be executed after properties + * changes. + */ + @Autowired + private PropertyUpdateExecutor propertyUpdateExecutor; + + /** + * Shutdown service for executing restarts. + */ + @Autowired + private ShutdownService shutdownService; + + /** + * {@link JAXBTransformator}. + *

+ * Can not auto-wire this component at the point of start-up, because it's a component. Thus, + * direct access. + */ + private JAXBTransformator transformator = new JAXBTransformator(); + + /** + * Returns the currently existing {@link PropertySection} in the CMR configuration. + * + * @return Returns the currently existing {@link PropertySection} in the CMR configuration. + */ + public Collection getConfigurationPropertySections() { + return configuration.getSections(); + } + + /** + * Returns the currently CMR configuration. + * + * @return Returns the currently CMR configuration. + */ + Configuration getConfiguration() { + return configuration; + } + + /** + * Updates the current configuration of the CMR. + * + * @param update + * {@link ConfigurationUpdate} containing all {@link AbstractPropertyUpdate}s that + * should be reflected in the current configuration of the CMR. + * @param executeRestart + * Should restart be automatically executed after properties update. + * @throws Exception + * If update is not valid + */ + public synchronized void updateConfiguration(ConfigurationUpdate update, boolean executeRestart) throws Exception { + // first validate all changes + // if property does not exist or can not be updated throw exception + for (IPropertyUpdate propertyUpdate : update.getPropertyUpdates()) { + SingleProperty property = (SingleProperty) configuration.forLogicalName(propertyUpdate.getPropertyLogicalName()); + if (null == property) { + throw new Exception("Property " + propertyUpdate.getPropertyLogicalName() + " can not be updated because the property does not exist in the current configuration."); + } else if (!property.canUpdate(propertyUpdate)) { + throw new Exception("Property " + propertyUpdate.getPropertyLogicalName() + " can not be updated because the property update value is not valid."); + } + } + + // if all valid update all + List> updatedProperties = new ArrayList<>(); + for (IPropertyUpdate propertyUpdate : update.getPropertyUpdates()) { + SingleProperty property = (SingleProperty) configuration.forLogicalName(propertyUpdate.getPropertyLogicalName()); + if (propertyUpdate.isRestoreDefault()) { + property.setToDefaultValue(); + } else { + property.setValue(propertyUpdate.getUpdateValue()); + } + updatedProperties.add(property); + + if (LOG.isInfoEnabled()) { + LOG.info("Property '" + property.getName() + "' successfully updated, new value is " + property.getFormattedValue()); + } + } + + // merge the update file + // note that restore to default updates will also be part of the update + if (null == configurationUpdate) { + configurationUpdate = update; + } else { + configurationUpdate.merge(update, true); + } + + // back up the old configuration file if it exists + if (Files.exists(getConfigurationUpdatePath())) { + String backupPathString = getConfigurationUpdatePath().toString() + "~" + System.currentTimeMillis() + ".backup"; + Path backupPath = Paths.get(backupPathString); + try { + Files.copy(getConfigurationUpdatePath(), backupPath, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + LOG.warn("Could not back up the current configuration update", e); + } + } + + // flush the new configuration update + try { + transformator.marshall(getConfigurationUpdatePath(), configurationUpdate, Paths.get(CONFIG_DIR).relativize(getConfigurationUpdateSchemaPath()).toString()); + } catch (JAXBException | IOException e) { + LOG.warn("Could not flush the new configuration update", e); + } + + if (executeRestart) { + // just restart, properties will be reloaded anyway + shutdownService.restart(); + } else { + // activate property update executor + propertyUpdateExecutor.executePropertyUpdates(updatedProperties); + } + } + + /** + * Returns {@link Properties} containing key/value property pairs defined in CMR configuration. + * + * @return Returns {@link Properties} containing key/value property pairs defined in CMR + * configuration. + */ + @Bean(name = LOCAL_PROPERTIES_BEAN_NAME) + protected synchronized Properties getProperties() { + try { + loadConfigurationAndUpdates(); + } catch (JAXBException | IOException | SAXException e) { + LOG.warn("|-Default CMR configuration can not be loaded.", e); + return new Properties(); + } + + // check if there is update file + if (null != configurationUpdate) { + LOG.info("|-Updates to the CMR Configuration found, applying the updates"); + + List> notValidList = new ArrayList<>(); + // if there is update file validate updates and set to the configuration + + for (IPropertyUpdate propertyUpdate : configurationUpdate.getPropertyUpdates()) { + SingleProperty property = (SingleProperty) configuration.forLogicalName(propertyUpdate.getPropertyLogicalName()); + + // if property does not exist or can not be update add to not valid + if (null == property || !property.canUpdate(propertyUpdate)) { + notValidList.add(propertyUpdate); + continue; + } + + if (propertyUpdate.isRestoreDefault()) { + property.setToDefaultValue(); + } else { + property.setValue(propertyUpdate.getUpdateValue()); + } + } + + // if not valid list is not empty + // log all wrong properties and rewrite the configuration update + if (CollectionUtils.isNotEmpty(notValidList)) { + for (IPropertyUpdate propertyUpdate : notValidList) { + configurationUpdate.removePropertyUpdate(propertyUpdate); + LOG.info("|-Update of the property " + propertyUpdate.getPropertyLogicalName() + + " can not be performed either because property does not exist in the default configuration or the update value is not valid"); + } + try { + transformator.marshall(getConfigurationUpdatePath(), configurationUpdate, Paths.get(CONFIG_DIR).relativize(getConfigurationUpdateSchemaPath()).toString()); + } catch (JAXBException | IOException e) { + LOG.warn("|-CMR Configuration update can not be re-written", e); + } + } + } else { + LOG.info("|-No CMR Configuration updates found, continuing to use default configuration"); + } + + // validate configuration + Map validationMap = configuration.validate(); + + // if we have some validation problems log them + if (MapUtils.isNotEmpty(validationMap)) { + for (Entry entry : validationMap.entrySet()) { + LOG.warn(entry.getValue().getMessage()); + } + } else { + LOG.info("|-CMR Configuration verified with no errors"); + } + + // create properties from correct ones + Properties properties = new Properties(); + for (AbstractProperty property : configuration.getAllProperties()) { + if (!validationMap.containsKey(property)) { + property.register(properties); + } + } + return properties; + } + + /** + * This is a workaround for the problem of providing the {@link PropertyManager} to the bean + * factory for auto-wiring. The problem is that because Properties are provided via @Bean + * annotation, the {@link PropertyManager} itself will not be completely auto-wired with needed + * dependencies. + * + * @return Object it self. + */ + @Bean + protected PropertyManager getPropertyManager() { + return this; + } + + /** + * @return Returns path to the default configuration path. + */ + Path getDefaultConfigurationPath() { + return Paths.get(CONFIG_DIR, DEFAULT_CONFIG_FILE); + } + + /** + * @return Returns path to the current configuration update path. + */ + Path getConfigurationUpdatePath() { + return Paths.get(CONFIG_DIR, CONFIG_UPDATE_FILE); + } + + /** + * @return Returns path to the configuration XSD schema file. + */ + Path getConfigurationSchemaPath() { + return Paths.get(SCHEMA_DIR, CONFIGURATION_SCHEMA_FILE); + } + + /** + * @return Returns path to the configuration update XSD schema file. + */ + Path getConfigurationUpdateSchemaPath() { + return Paths.get(SCHEMA_DIR, CONFIGURATION_UPDATE_SCHEMA_FILE); + } + + /** + * Loads the default configuration if it is not already loaded. If successfully loaded + * configuration will be placed in the {@link #configuration} field. + * + * + * @throws JAXBException + * If {@link JAXBException} occurs during loading. + * @throws IOException + * If {@link IOException} occurs during loading. + * @throws SAXException + * If {@link SAXException} occurs during schema parsing. + */ + void loadConfigurationAndUpdates() throws JAXBException, IOException, SAXException { + LOG.info("|-Loading the default CMR configuration"); + configuration = transformator.unmarshall(getDefaultConfigurationPath(), getConfigurationSchemaPath(), Configuration.class); + configurationUpdate = transformator.unmarshall(getConfigurationUpdatePath(), getConfigurationUpdateSchemaPath(), ConfigurationUpdate.class); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/property/PropertyUpdateExecutor.java b/CMR/src/info/novatec/inspectit/cmr/property/PropertyUpdateExecutor.java new file mode 100644 index 000000000..894ab57fb --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/property/PropertyUpdateExecutor.java @@ -0,0 +1,409 @@ +package info.novatec.inspectit.cmr.property; + +import info.novatec.inspectit.cmr.property.configuration.SingleProperty; +import info.novatec.inspectit.cmr.property.spring.PropertyUpdate; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.ArrayUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.stereotype.Component; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.ReflectionUtils.FieldCallback; +import org.springframework.util.ReflectionUtils.MethodCallback; + +/** + * This class executes method annotated with {@link PropertyUpdate} annotation. + * + * @author Ivan Senic + * + */ +@Component +public class PropertyUpdateExecutor implements BeanPostProcessor, BeanFactoryAware { + + /** + * The logger of this class. + *

+ * Must be declared manually because of the post processor attribute of this class. + */ + private static final Logger LOG = LoggerFactory.getLogger(PropertyUpdateExecutor.class); + + /** + * {@link ConfigurableListableBeanFactory}. + */ + private ConfigurableListableBeanFactory beanFactory; + + /** + * List of all collected {@link PropertyUpdateFieldInfo} objects. + */ + private List fieldInfoList = new ArrayList<>(); + + /** + * List of all collected {@link PropertyUpdateMethodInfo} objects. + */ + private List methodInfoList = new ArrayList<>(); + + /** + * Executes the methods that declare the {@link PropertyUpdate} annotations if the list of + * updated properties names matches the ones specified in the annotation. + * + * @param properties + * List of updated properties. + */ + public void executePropertyUpdates(List> properties) { + // first update all fields + for (SingleProperty singleProperty : properties) { + for (PropertyUpdateFieldInfo fieldInfo : fieldInfoList) { + if (fieldInfo.isPropertyMatching(singleProperty)) { + Object value = singleProperty.getValue(); + if (!fieldInfo.getField().getType().equals(value.getClass())) { + // if classes are not matching try with spring type converter + value = beanFactory.getTypeConverter().convertIfNecessary(value, fieldInfo.getField().getType()); + } + ReflectionUtils.setField(fieldInfo.getField(), fieldInfo.getTarget(), value); + + if (LOG.isDebugEnabled()) { + LOG.debug("Updated field " + fieldInfo.getField().getName() + " on object " + fieldInfo.getTarget() + " with value " + value + + ". The field was updated because of the updated property " + fieldInfo.getProperty()); + } + } + } + } + + // then execute all update methods + Set methodsToExecute = new HashSet<>(); + for (PropertyUpdateMethodInfo methodInfo : methodInfoList) { + if (!methodsToExecute.contains(methodInfo) && methodInfo.arePropertiesMatching(properties)) { + methodsToExecute.add(methodInfo); + } + } + + if (CollectionUtils.isNotEmpty(methodsToExecute)) { + for (PropertyUpdateMethodInfo methodInfo : methodsToExecute) { + ReflectionUtils.invokeMethod(methodInfo.getMethod(), methodInfo.getTarget()); + + if (LOG.isDebugEnabled()) { + LOG.debug("Invoked the method " + methodInfo.getMethod().toGenericString() + " on target object " + methodInfo.getTarget() + + ". The method was invoked cause it defines the following properties of which at least one was updated: " + Arrays.toString(methodInfo.getProperties())); + } + } + } + + } + + /** + * {@inheritDoc} + */ + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + /** + * {@inheritDoc} + */ + @Override + public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException { + // process methods for @PropertyUpdate + ReflectionUtils.doWithMethods(bean.getClass(), new MethodCallback() { + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + // make sure only no-arg methods with annotation are added + if (method.isAnnotationPresent(PropertyUpdate.class) && ArrayUtils.isEmpty(method.getParameterTypes())) { + PropertyUpdate propertyUpdate = method.getAnnotation(PropertyUpdate.class); + if (ArrayUtils.isNotEmpty(propertyUpdate.properties())) { + ReflectionUtils.makeAccessible(method); + PropertyUpdateMethodInfo methodInfo = new PropertyUpdateMethodInfo(bean, method, propertyUpdate.properties()); + methodInfoList.add(methodInfo); + } + } + } + }); + + // process fields for @Value + ReflectionUtils.doWithFields(bean.getClass(), new FieldCallback() { + @Override + public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { + if (field.isAnnotationPresent(Value.class)) { + // we must skip final fields + if (Modifier.isFinal(field.getModifiers())) { + LOG.warn("Field " + field.getName() + " of bean " + beanName + + " defines @Value annotation, although it's declared as final. This field can not be updated if its property value is changed."); + return; + } + + Value value = field.getAnnotation(Value.class); + String placeholder = value.value(); + int startChar = placeholder.indexOf('{'); + int endChar = placeholder.indexOf('}'); + String property; + if (startChar > 0 && endChar > startChar) { + property = placeholder.substring(startChar + 1, endChar); + } else { + property = placeholder; + } + ReflectionUtils.makeAccessible(field); + PropertyUpdateFieldInfo fieldInfo = new PropertyUpdateFieldInfo(bean, field, property); + fieldInfoList.add(fieldInfo); + } + } + }); + + return bean; + } + + /** + * {@inheritDoc} + */ + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (!(beanFactory instanceof ConfigurableListableBeanFactory)) { + throw new IllegalArgumentException("PropertyUpdateExecutor requires a ConfigurableListableBeanFactory"); + } + this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; + } + + /** + * Class that combines all needed information for one field that needs to be updated when + * certain property is changed. + * + * @author Ivan Senic + * + */ + private static final class PropertyUpdateFieldInfo { + + /** + * Target object. + */ + private final Object target; + + /** + * Field that needs to be updated. + */ + private final Field field; + + /** + * Property name. + */ + private final String property; + + /** + * @param target + * Target object. + * @param field + * Field that needs to be updated. + * @param property + * Property name. + */ + public PropertyUpdateFieldInfo(Object target, Field field, String property) { + if (null == target) { + throw new IllegalArgumentException("Target object can not be null."); + } + if (null == field) { + throw new IllegalArgumentException("Field to update can not be null."); + } + if (null == property) { + throw new IllegalArgumentException("Property name can not be null."); + } + this.target = target; + this.field = field; + this.property = property; + } + + /** + * Gets {@link #target}. + * + * @return {@link #target} + */ + public Object getTarget() { + return target; + } + + /** + * Gets {@link #field}. + * + * @return {@link #field} + */ + public Field getField() { + return field; + } + + /** + * Gets {@link #property}. + * + * @return {@link #property} + */ + public String getProperty() { + return property; + } + + /** + * Returns true if the name of the property field is bounded to is matching the logical name + * of the update property. + * + * @param updatedProperty + * {@link SingleProperty}. + * @return Returns true if the name of the property field is bounded to is matching the + * logical name of the update property. + */ + public boolean isPropertyMatching(SingleProperty updatedProperty) { + return property.equals(updatedProperty.getLogicalName()); + } + } + + /** + * Class that combines all needed information for one method that needs to be executed when + * certain properties are changed. + * + * @author Ivan Senic + * + */ + private static final class PropertyUpdateMethodInfo { + + /** + * Target object. + */ + private final Object target; + + /** + * Method that should be executed. + */ + private final Method method; + + /** + * List of properties to react upon change. + */ + private final String[] properties; + + /** + * Default constructor. + * + * @param target + * Target object. + * @param method + * Method that should be executed. + * @param properties + * List of properties to react upon change. + */ + public PropertyUpdateMethodInfo(Object target, Method method, String[] properties) { + if (null == target) { + throw new IllegalArgumentException("Target object can not be null."); + } + if (null == method) { + throw new IllegalArgumentException("Method to invoke can not be null."); + } + if (ArrayUtils.isEmpty(properties)) { + throw new IllegalArgumentException("Property array can not be empty."); + } + this.target = target; + this.method = method; + this.properties = properties; + } + + /** + * Gets {@link #target}. + * + * @return {@link #target} + */ + public Object getTarget() { + return target; + } + + /** + * Gets {@link #method}. + * + * @return {@link #method} + */ + public Method getMethod() { + return method; + } + + /** + * Gets {@link #properties}. + * + * @return {@link #properties} + */ + public String[] getProperties() { + return properties; + } + + /** + * Returns true if given list of properties are matching any property name in this + * {@link PropertyUpdateMethodInfo} object. + * + * @param updatedProperties + * Updated properties. + * @return Returns true if given list of are matching any property name in this + * {@link PropertyUpdateMethodInfo} object. + */ + public boolean arePropertiesMatching(List> updatedProperties) { + if (CollectionUtils.isNotEmpty(updatedProperties)) { + for (SingleProperty property : updatedProperties) { + if (ArrayUtils.contains(properties, property.getLogicalName())) { + return true; + } + } + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((method == null) ? 0 : method.hashCode()); + result = prime * result + ((target == null) ? 0 : System.identityHashCode(target)); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PropertyUpdateMethodInfo other = (PropertyUpdateMethodInfo) obj; + if (method == null) { + if (other.method != null) { + return false; + } + } else if (!method.equals(other.method)) { + return false; + } + if (target == null) { + if (other.target != null) { + return false; + } + } else if (System.identityHashCode(target) != System.identityHashCode(other.target)) { + return false; + } + return true; + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/rmi/KryoNetServerCreator.java b/CMR/src/info/novatec/inspectit/cmr/rmi/KryoNetServerCreator.java new file mode 100644 index 000000000..107e689b4 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/rmi/KryoNetServerCreator.java @@ -0,0 +1,113 @@ +package info.novatec.inspectit.cmr.rmi; + +import info.novatec.inspectit.kryonet.Connection; +import info.novatec.inspectit.kryonet.ExtendedSerializationImpl; +import info.novatec.inspectit.kryonet.IExtendedSerialization; +import info.novatec.inspectit.kryonet.Listener; +import info.novatec.inspectit.kryonet.Server; +import info.novatec.inspectit.kryonet.rmi.ObjectSpace; +import info.novatec.inspectit.spring.logger.Log; +import info.novatec.inspectit.storage.nio.stream.StreamProvider; +import info.novatec.inspectit.storage.serializer.provider.SerializationManagerProvider; + +import java.io.IOException; +import java.util.concurrent.ExecutorService; + +import org.slf4j.Logger; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; + +import com.esotericsoftware.kryo.Kryo; + +/** + * COnfiguration of the {@link Server} that will be used for communication with the agent. + * + * @author Ivan Senic + * + */ +@Configuration +public class KryoNetServerCreator { + + /** + * Logger for the class. + */ + @Log + Logger log; + + /** + * Port to bind service to. + */ + @Value("${cmr.port}") + private int port; + + /** + * Serialization manager to provide {@link Kryo} instance. + */ + @Autowired + private SerializationManagerProvider serializationManagerProvider; + + /** + * {@link StreamProvider} to hook with the KryoNet. + */ + @Autowired + private StreamProvider streamProvider; + + /** + * Executor service for object space. This will enable that multiple incoming communication + * requests can be handled in parallel. + */ + @Autowired + @Qualifier("kryoNetObjectSpaceExecutorService") + private ExecutorService executorService; + + /** + * Start the kryonet server and binds it to the specified port. + * + * @return Start the kryonet server and binds it to the specified port. + */ + @Bean(name = "kryonet-server", destroyMethod = "stop") + public Server createServer() { + IExtendedSerialization serialization = new ExtendedSerializationImpl(serializationManagerProvider); + + Server server = new Server(serialization, streamProvider); + server.start(); + + try { + server.bind(port); + log.info("|-Kryonet server successfully started and running on port " + port); + } catch (IOException e) { + throw new BeanInitializationException("Could not bind the kryonet server to the specified ports.", e); + } + + return server; + } + + /** + * Creates the {@link ObjectSpace}, registers kryo classes and connect the space to the server. + * + * @param server + * KryoNet {@link Server}. + * @return Created {@link ObjectSpace}. + */ + @Bean(name = "kryonet-server-objectspace") + @DependsOn("kryonet-server") + @Autowired + public ObjectSpace createObjectSpace(Server server) { + final ObjectSpace objectSpace = new ObjectSpace(); + objectSpace.setExecutor(executorService); + server.addListener(new Listener() { + @Override + public void connected(Connection connection) { + objectSpace.addConnection(connection); + } + }); + + return objectSpace; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/service/AgentStorageService.java b/CMR/src/info/novatec/inspectit/cmr/service/AgentStorageService.java new file mode 100644 index 000000000..59596b4d4 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/service/AgentStorageService.java @@ -0,0 +1,231 @@ +package info.novatec.inspectit.cmr.service; + +import info.novatec.inspectit.cmr.dao.DefaultDataDao; +import info.novatec.inspectit.cmr.property.spring.PropertyUpdate; +import info.novatec.inspectit.cmr.spring.aop.MethodLog; +import info.novatec.inspectit.cmr.util.AgentStatusDataProvider; +import info.novatec.inspectit.cmr.util.Converter; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.spring.logger.Log; + +import java.lang.ref.SoftReference; +import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.TimeUnit; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +/** + * The default implementation of the {@link IAgentStorageService} interface. Uses an implementation + * of the {@link DefaultDataDao} interface to save and retrieve the data objects from the database. + * + * @author Patrice Bouillet + * + */ +@Service +public class AgentStorageService implements IAgentStorageService { + + /** The logger of this class. */ + @Log + Logger log; + + /** + * Queue capacity for incoming data. + */ + private static final int QUEUE_CAPACITY = 50; + + /** + * Amount of milliseconds after which the data is thrown away if queue is full. + */ + private static final long DATA_THROW_TIMEOUT_MILLIS = 10; + + /** + * The default data DAO. + */ + @Autowired + private DefaultDataDao defaultDataDao; + + /** + * {@link AgentStatusDataProvider}. + */ + @Autowired + AgentStatusDataProvider platformIdentDateSaver; + + /** + * {@link CmrManagementService}. + */ + @Autowired + ICmrManagementService cmrManagementService; + + /** + * Queue to store and remove list of data that has to be processed. + */ + private ArrayBlockingQueue>> dataObjectsBlockingQueue = new ArrayBlockingQueue>>(QUEUE_CAPACITY); + + /** + * Count of thread to process data. + */ + @Value("${cmr.agentStorageServiceThreadCount}") + private int threadCount; + + /** + * List of currently active threads that process the data. + */ + private List threadList = new ArrayList<>(); + + /** + * Default constructor. + */ + public AgentStorageService() { + } + + /** + * Constructor that can be used in testing for suppling the queue. + * + * @param dataObjectsBlockingQueue + * Queue. + */ + AgentStorageService(ArrayBlockingQueue>> dataObjectsBlockingQueue) { + this.dataObjectsBlockingQueue = dataObjectsBlockingQueue; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public void addDataObjects(final List dataObjects) throws RemoteException { + SoftReference> softReference = new SoftReference>(dataObjects); + if (!dataObjects.isEmpty()) { + platformIdentDateSaver.registerDataSent(dataObjects.get(0).getPlatformIdent()); + } + try { + boolean added = dataObjectsBlockingQueue.offer(softReference, DATA_THROW_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + if (!added) { + int droppedSize = dataObjects.size(); + if (log.isTraceEnabled()) { + log.trace("Data dropped on the CMR due to the high volume of incoming data from Agent(s). Dropped data objects count: " + droppedSize); + } + cmrManagementService.addDroppedDataCount(droppedSize); + } + } catch (InterruptedException e) { + return; + } + } + + /** + * Updates the number of data processing threads. The new number of threads should be defined in + * {@link #threadCount} before calling this method. + *

+ * This is an automated properties update execution method. + */ + @PropertyUpdate(properties = { "cmr.agentStorageServiceThreadCount" }) + public synchronized void updateThreadCount() { + if (threadCount <= 0) { + threadCount = 1; + } + + int threadListSize = threadList.size(); + if (threadCount < threadListSize) { + // remove threads + for (int i = 0; i < threadListSize - threadCount; i++) { + Thread thread = threadList.remove(i); + thread.interrupt(); + try { + thread.join(); + } catch (InterruptedException e) { + Thread.interrupted(); + } + } + } else if (threadCount > threadListSize) { + // add new threads + for (int i = threadListSize; i < threadCount; i++) { + ProcessDataThread processDataThread = new ProcessDataThread(i); + processDataThread.start(); + threadList.add(processDataThread); + } + + } + } + + /** + * Is executed after dependency injection is done to perform any initialization. + * + * @throws Exception + * if an error occurs during {@link PostConstruct} + */ + @PostConstruct + public void postConstruct() throws Exception { + updateThreadCount(); + + if (log.isInfoEnabled()) { + log.info("|-Agent Storage Service active..."); + } + + } + + /** + * Thread class that is processing the data coming to the Agent service and invoking the + * {@link DefaultDataDao}. + * + * @author Ivan Senic + * + */ + private class ProcessDataThread extends Thread { + + /** + * Default constructor. + * + * @param threadId + * Id of the thread that will be added to the thread name. + */ + public ProcessDataThread(int threadId) { + setName("agent-storage-service-process-data-thread-" + threadId); + setDaemon(true); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + while (true) { + if (isInterrupted()) { + break; + } + + SoftReference> softReference = null; + try { + softReference = dataObjectsBlockingQueue.take(); + } catch (InterruptedException e) { + this.interrupt(); + return; + } + + List defaultDataList = softReference.get(); + if (defaultDataList != null) { + for (DefaultData data : defaultDataList) { + data.finalizeData(); + } + + long time = 0; + if (log.isDebugEnabled()) { + time = System.nanoTime(); + } + + defaultDataDao.saveAll(defaultDataList); + + if (log.isDebugEnabled()) { + log.debug("Data Objects count: " + defaultDataList.size() + " Save duration: " + Converter.nanoToMilliseconds(System.nanoTime() - time)); + } + } + } + } + } +} diff --git a/CMR/src/info/novatec/inspectit/cmr/service/CmrManagementService.java b/CMR/src/info/novatec/inspectit/cmr/service/CmrManagementService.java new file mode 100644 index 000000000..059ef4af0 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/service/CmrManagementService.java @@ -0,0 +1,207 @@ +package info.novatec.inspectit.cmr.service; + +import info.novatec.inspectit.cmr.cache.IBuffer; +import info.novatec.inspectit.cmr.property.PropertyManager; +import info.novatec.inspectit.cmr.property.configuration.PropertySection; +import info.novatec.inspectit.cmr.property.update.configuration.ConfigurationUpdate; +import info.novatec.inspectit.cmr.spring.aop.MethodLog; +import info.novatec.inspectit.cmr.util.ShutdownService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.cmr.CmrStatusData; +import info.novatec.inspectit.spring.logger.Log; +import info.novatec.inspectit.storage.StorageManager; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collection; +import java.util.Date; + +import javax.annotation.PostConstruct; + +import org.apache.commons.lang.mutable.MutableLong; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * Implementation of the {@link ICmrManagementService}. Provides general management of the CMR. + * + * @author Ivan Senic + * + */ +@Service +public class CmrManagementService implements ICmrManagementService { + + /** + * Name of the folder where database is stored. + */ + private static final String DATABASE_FOLDER = "db"; + + /** The logger of this class. */ + @Log + Logger log; + + /** + * Buffer data dao. + */ + @Autowired + private IBuffer buffer; + + /** + * {@link StorageManager}. + */ + @Autowired + private StorageManager storageManager; + + /** + * {@link PropertyManager}. + */ + @Autowired + private PropertyManager propertyManager; + + /** + * Count of dropped data due to high volume of incoming data objects. + */ + private int droppedDataCount = 0; + + /** + * {@link ShutdownService}. + */ + @Autowired + private ShutdownService shutdownService; + + /** + * Time in milliseconds when the CMR has started. + */ + private long timeStarted; + + /** + * Date when the CMR has started. + */ + private Date dateStarted; + + /** + * {@inheritDoc} + */ + @Override + public void restart() { + shutdownService.restart(); + } + + /** + * {@inheritDoc} + */ + @Override + public void shutdown() { + shutdownService.shutdown(); + } + + /** + * {@inheritDoc} + */ + @MethodLog + public void clearBuffer() { + buffer.clearAll(); + } + + /** + * {@inheritDoc} + */ + @MethodLog + public CmrStatusData getCmrStatusData() { + // cmr status data should always report in bytes! + CmrStatusData cmrStatusData = new CmrStatusData(); + cmrStatusData.setCurrentBufferSize(buffer.getCurrentSize()); + cmrStatusData.setMaxBufferSize(buffer.getMaxSize()); + cmrStatusData.setBufferOldestElement(buffer.getOldestElement()); + cmrStatusData.setBufferNewestElement(buffer.getNewestElement()); + cmrStatusData.setStorageDataSpaceLeft(storageManager.getBytesHardDriveOccupancyLeft()); + cmrStatusData.setStorageMaxDataSpace(storageManager.getMaxBytesHardDriveOccupancy()); + cmrStatusData.setWarnSpaceLeftActive(storageManager.isSpaceWarnActive()); + cmrStatusData.setCanWriteMore(storageManager.canWriteMore()); + cmrStatusData.setUpTime(System.currentTimeMillis() - timeStarted); + cmrStatusData.setDateStarted(dateStarted); + cmrStatusData.setDatabaseSize(getDatabaseSize()); + return cmrStatusData; + } + + /** + * Reports that an amount of data has been dropped. + * + * @param count + * Dropped amount. + */ + public void addDroppedDataCount(int count) { + droppedDataCount += count; + } + + /** + * {@inheritDoc} + */ + public int getDroppedDataCount() { + return droppedDataCount; + } + + /** + * {@inheritDoc} + */ + public Collection getConfigurationPropertySections() { + return propertyManager.getConfigurationPropertySections(); + } + + /** + * {@inheritDoc} + */ + @Override + public void updateConfiguration(ConfigurationUpdate configurationUpdate, boolean executeRestart) throws Exception { + propertyManager.updateConfiguration(configurationUpdate, executeRestart); + } + + /** + * Returns the {@link Long} holding the size of the database folder or null if + * database folder does not exists or calculation of size fails. + * + * @return Returns the {@link Long} holding the size of the database folder or null + * if database folder does not exists or calculation of size fails. + */ + private Long getDatabaseSize() { + Path databaseFolder = Paths.get(DATABASE_FOLDER); + if (Files.notExists(databaseFolder) || !Files.isDirectory(databaseFolder)) { + return null; + } + final MutableLong size = new MutableLong(); + try { + Files.walkFileTree(databaseFolder, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + size.add(attrs.size()); + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + return null; + } + return Long.valueOf(size.longValue()); + } + + /** + * Is executed after dependency injection is done to perform any initialization. + * + * @throws Exception + * if an error occurs during {@link PostConstruct} + */ + @PostConstruct + public void postConstruct() throws Exception { + timeStarted = System.currentTimeMillis(); + dateStarted = new Date(timeStarted); + if (log.isInfoEnabled()) { + log.info("|-CMR Management Service active..."); + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/service/ExceptionDataAccessService.java b/CMR/src/info/novatec/inspectit/cmr/service/ExceptionDataAccessService.java new file mode 100644 index 000000000..f73e80bc5 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/service/ExceptionDataAccessService.java @@ -0,0 +1,141 @@ +package info.novatec.inspectit.cmr.service; + +import info.novatec.inspectit.cmr.dao.ExceptionSensorDataDao; +import info.novatec.inspectit.cmr.service.cache.CachedDataService; +import info.novatec.inspectit.cmr.spring.aop.MethodLog; +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.communication.data.AggregatedExceptionSensorData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.spring.logger.Log; + +import java.util.Date; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * Service class for retrieving {@link ExceptionSensorData} objects from the CMR. + * + * @author Eduard Tudenhoefner + * + */ +@Service +public class ExceptionDataAccessService implements IExceptionDataAccessService { + + /** The logger of this class. */ + @Log + Logger log; + + /** + * The exception sensor DAO. + */ + @Autowired + private ExceptionSensorDataDao exceptionSensorDataDao; + + /** + * {@link CachedDataService}. + */ + @Autowired + private ICachedDataService cachedDataService; + + /** + * {@inheritDoc} + */ + @MethodLog + public List getUngroupedExceptionOverview(ExceptionSensorData template, int limit, ResultComparator resultComparator) { + if (null != resultComparator) { + resultComparator.setCachedDataService(cachedDataService); + } + List result = exceptionSensorDataDao.getUngroupedExceptionOverview(template, limit, resultComparator); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getUngroupedExceptionOverview(ExceptionSensorData template, int limit, Date fromDate, Date toDate, ResultComparator resultComparator) { + if (null != resultComparator) { + resultComparator.setCachedDataService(cachedDataService); + } + List result = exceptionSensorDataDao.getUngroupedExceptionOverview(template, limit, fromDate, toDate, resultComparator); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getUngroupedExceptionOverview(ExceptionSensorData template, ResultComparator resultComparator) { + if (null != resultComparator) { + resultComparator.setCachedDataService(cachedDataService); + } + List result = exceptionSensorDataDao.getUngroupedExceptionOverview(template, resultComparator); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getUngroupedExceptionOverview(ExceptionSensorData template, Date fromDate, Date toDate, ResultComparator resultComparator) { + if (null != resultComparator) { + resultComparator.setCachedDataService(cachedDataService); + } + List result = exceptionSensorDataDao.getUngroupedExceptionOverview(template, fromDate, toDate, resultComparator); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getExceptionTree(ExceptionSensorData template) { + List result = exceptionSensorDataDao.getExceptionTree(template); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getDataForGroupedExceptionOverview(ExceptionSensorData template) { + List result = exceptionSensorDataDao.getDataForGroupedExceptionOverview(template); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getDataForGroupedExceptionOverview(ExceptionSensorData template, Date fromDate, Date toDate) { + List result = exceptionSensorDataDao.getDataForGroupedExceptionOverview(template, fromDate, toDate); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getStackTraceMessagesForThrowableType(ExceptionSensorData template) { + List result = exceptionSensorDataDao.getStackTraceMessagesForThrowableType(template); + return result; + } + + /** + * Is executed after dependency injection is done to perform any initialization. + * + * @throws Exception + * if an error occurs during {@link PostConstruct} + */ + @PostConstruct + public void postConstruct() throws Exception { + if (log.isInfoEnabled()) { + log.info("|-Exception Sensor Data Access Service active..."); + } + } +} diff --git a/CMR/src/info/novatec/inspectit/cmr/service/GlobalDataAccessService.java b/CMR/src/info/novatec/inspectit/cmr/service/GlobalDataAccessService.java new file mode 100644 index 000000000..7fa68cf96 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/service/GlobalDataAccessService.java @@ -0,0 +1,189 @@ +package info.novatec.inspectit.cmr.service; + +import info.novatec.inspectit.cmr.dao.DefaultDataDao; +import info.novatec.inspectit.cmr.dao.PlatformIdentDao; +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.cmr.service.exception.ServiceException; +import info.novatec.inspectit.cmr.spring.aop.MethodLog; +import info.novatec.inspectit.cmr.util.AgentStatusDataProvider; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.cmr.AgentStatusData; +import info.novatec.inspectit.communication.data.cmr.AgentStatusData.AgentConnection; +import info.novatec.inspectit.spring.logger.Log; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author Patrice Bouillet + * + */ +@Service +public class GlobalDataAccessService implements IGlobalDataAccessService { + + /** The logger of this class. */ + @Log + Logger log; + + /** + * The platform ident DAO. + */ + @Autowired + PlatformIdentDao platformIdentDao; + + /** + * The default data DAO. + */ + @Autowired + DefaultDataDao defaultDataDao; + + /** + * {@link AgentStatusDataProvider}. + */ + @Autowired + AgentStatusDataProvider agentStatusProvider; + + /** + * {@inheritDoc} + */ + @MethodLog + public Map getAgentsOverview() { + List agents = platformIdentDao.findAll(); + Map agentStatusMap = agentStatusProvider.getAgentStatusDataMap(); + + Map resultMap = new HashMap(); + for (PlatformIdent platformIdent : agents) { + resultMap.put(platformIdent, agentStatusMap.get(platformIdent.getId())); + } + return resultMap; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public PlatformIdent getCompleteAgent(long id) throws ServiceException { + PlatformIdent platformIdent = platformIdentDao.findInitialized(id); + if (null != platformIdent) { + return platformIdent; + } else { + throw new ServiceException("Agent with given ID=" + id + " is not existing."); + } + } + + /** + * + * {@inheritDoc} + */ + @MethodLog + public void deleteAgent(long platformId) throws ServiceException { + PlatformIdent platformIdent = platformIdentDao.load(platformId); + if (null != platformIdent) { + AgentStatusData agentStatusData = agentStatusProvider.getAgentStatusDataMap().get(platformIdent.getId()); + + // delete is allowed only if agent is disconnected or was never connected + if (null != agentStatusData && agentStatusData.getAgentConnection() == AgentConnection.CONNECTED) { + throw new ServiceException("The Agent '" + platformIdent.getAgentName() + "' can not be deleted because it's still connected."); + } + + platformIdentDao.delete(platformIdent); + defaultDataDao.deleteAll(platformIdent.getId()); + agentStatusProvider.registerDeleted(platformId); + + log.info("The Agent '" + platformIdent.getAgentName() + "' with the ID " + platformIdent.getId() + " was successfully deleted from the CMR."); + } else { + throw new ServiceException("The Agent with the ID=" + platformId + " does not exists on the CMR."); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getLastDataObjects(DefaultData template, long timeInterval) { + List result = defaultDataDao.findByExampleWithLastInterval(template, timeInterval); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public DefaultData getLastDataObject(DefaultData template) { + DefaultData result = defaultDataDao.findByExampleLastData(template); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getDataObjectsFromToDate(DefaultData template, Date fromDate, Date toDate) { + if (fromDate.after(toDate)) { + return Collections.emptyList(); + } + + List result = defaultDataDao.findByExampleFromToDate(template, fromDate, toDate); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getTemplatesDataObjectsFromToDate(Collection templates, Date fromDate, Date toDate) { + if (fromDate.after(toDate)) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(); + for (DefaultData template : templates) { + result.addAll(this.getDataObjectsFromToDate(template, fromDate, toDate)); + } + + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getDataObjectsSinceId(DefaultData template) { + List result = defaultDataDao.findByExampleSinceId(template); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getDataObjectsSinceIdIgnoreMethodId(DefaultData template) { + List result = defaultDataDao.findByExampleSinceIdIgnoreMethodId(template); + return result; + } + + /** + * Is executed after dependency injection is done to perform any initialization. + * + * @throws Exception + * if an error occurs during {@link PostConstruct} + */ + @PostConstruct + public void postConstruct() throws Exception { + if (log.isInfoEnabled()) { + log.info("|-Global Data Access Service active..."); + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/service/HttpTimerDataAccessService.java b/CMR/src/info/novatec/inspectit/cmr/service/HttpTimerDataAccessService.java new file mode 100644 index 000000000..21be1d92b --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/service/HttpTimerDataAccessService.java @@ -0,0 +1,88 @@ +package info.novatec.inspectit.cmr.service; + +import info.novatec.inspectit.cmr.dao.DefaultDataDao; +import info.novatec.inspectit.cmr.dao.HttpTimerDataDao; +import info.novatec.inspectit.cmr.spring.aop.MethodLog; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.spring.logger.Log; + +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import org.slf4j.Logger; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * This class provides access to the http related data in the CMR. + * + * @author Stefan Siegl + */ +@Service +public class HttpTimerDataAccessService implements IHttpTimerDataAccessService, InitializingBean { + + /** The logger of this class. */ + @Log + Logger log; + + /** + * The Dao. + */ + @Autowired + private HttpTimerDataDao dao; + + /** + * Default data dao. + */ + @Autowired + private DefaultDataDao defaultDataDao; + + /** + * {@inheritDoc} + */ + @MethodLog + public List getAggregatedTimerData(HttpTimerData httpData, boolean includeRequestMethod) { + return dao.getAggregatedHttpTimerData(httpData, includeRequestMethod); + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getAggregatedTimerData(HttpTimerData httpData, boolean includeRequestMethod, Date fromDate, Date toDate) { + return dao.getAggregatedHttpTimerData(httpData, includeRequestMethod, fromDate, toDate); + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getTaggedAggregatedTimerData(HttpTimerData httpData, boolean includeRequestMethod) { + return dao.getTaggedAggregatedHttpTimerData(httpData, includeRequestMethod); + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getTaggedAggregatedTimerData(HttpTimerData httpData, boolean includeRequestMethod, Date fromDate, Date toDate) { + return dao.getTaggedAggregatedHttpTimerData(httpData, includeRequestMethod, fromDate, toDate); + } + + @Override + public List getChartingHttpTimerDataFromDateToDate(Collection templates, Date fromDate, Date toDate, boolean retrieveByTag) { + return defaultDataDao.getChartingHttpTimerDataFromDateToDate(templates, fromDate, toDate, retrieveByTag); + } + + /** + * {@inheritDoc} + */ + public void afterPropertiesSet() throws Exception { + if (log.isInfoEnabled()) { + log.info("|-Http Timer Data Access Service active..."); + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/service/InvocationDataAccessService.java b/CMR/src/info/novatec/inspectit/cmr/service/InvocationDataAccessService.java new file mode 100644 index 000000000..270cfbb84 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/service/InvocationDataAccessService.java @@ -0,0 +1,124 @@ +package info.novatec.inspectit.cmr.service; + +import info.novatec.inspectit.cmr.dao.InvocationDataDao; +import info.novatec.inspectit.cmr.spring.aop.MethodLog; +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.spring.logger.Log; + +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author Patrice Bouillet + * + */ +@Service +public class InvocationDataAccessService implements IInvocationDataAccessService { + + /** The logger of this class. */ + @Log + Logger log; + + /** + * The invocation DAO. + */ + @Autowired + private InvocationDataDao invocationDataDao; + + /** + * The cached data service for {@link ResultComparator} bounding. + */ + @Autowired + private ICachedDataService cachedDataService; + + /** + * {@inheritDoc} + */ + @MethodLog + public List getInvocationSequenceOverview(long platformId, int limit, ResultComparator resultComparator) { + if (null != resultComparator) { + resultComparator.setCachedDataService(cachedDataService); + } + List result = invocationDataDao.getInvocationSequenceOverview(platformId, limit, resultComparator); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getInvocationSequenceOverview(long platformId, long methodId, int limit, ResultComparator resultComparator) { + if (null != resultComparator) { + resultComparator.setCachedDataService(cachedDataService); + } + List result = invocationDataDao.getInvocationSequenceOverview(platformId, methodId, limit, resultComparator); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getInvocationSequenceOverview(long platformId, int limit, Date fromDate, Date toDate, ResultComparator resultComparator) { + if (null != resultComparator) { + resultComparator.setCachedDataService(cachedDataService); + } + List result = invocationDataDao.getInvocationSequenceOverview(platformId, limit, fromDate, toDate, resultComparator); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getInvocationSequenceOverview(long platformId, long methodId, int limit, Date fromDate, Date toDate, ResultComparator resultComparator) { + if (null != resultComparator) { + resultComparator.setCachedDataService(cachedDataService); + } + List result = invocationDataDao.getInvocationSequenceOverview(platformId, methodId, limit, fromDate, toDate, resultComparator); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getInvocationSequenceOverview(long platformId, Collection invocationIdCollection, int limit, ResultComparator resultComparator) { + if (null != resultComparator) { + resultComparator.setCachedDataService(cachedDataService); + } + List result = invocationDataDao.getInvocationSequenceOverview(platformId, invocationIdCollection, limit, resultComparator); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public InvocationSequenceData getInvocationSequenceDetail(InvocationSequenceData template) { + InvocationSequenceData result = invocationDataDao.getInvocationSequenceDetail(template); + return result; + } + + /** + * Is executed after dependency injection is done to perform any initialization. + * + * @throws Exception + * if an error occurs during {@link PostConstruct} + */ + @PostConstruct + public void postConstruct() throws Exception { + if (log.isInfoEnabled()) { + log.info("|-Invocation Data Access Service active..."); + } + } + +} \ No newline at end of file diff --git a/CMR/src/info/novatec/inspectit/cmr/service/RegistrationService.java b/CMR/src/info/novatec/inspectit/cmr/service/RegistrationService.java new file mode 100644 index 000000000..ca07d78b6 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/service/RegistrationService.java @@ -0,0 +1,323 @@ +package info.novatec.inspectit.cmr.service; + +import info.novatec.inspectit.cmr.dao.MethodIdentDao; +import info.novatec.inspectit.cmr.dao.MethodIdentToSensorTypeDao; +import info.novatec.inspectit.cmr.dao.MethodSensorTypeIdentDao; +import info.novatec.inspectit.cmr.dao.PlatformIdentDao; +import info.novatec.inspectit.cmr.dao.PlatformSensorTypeIdentDao; +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.model.MethodIdentToSensorType; +import info.novatec.inspectit.cmr.model.MethodSensorTypeIdent; +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.cmr.model.PlatformSensorTypeIdent; +import info.novatec.inspectit.cmr.model.SensorTypeIdent; +import info.novatec.inspectit.cmr.service.exception.ServiceException; +import info.novatec.inspectit.cmr.spring.aop.MethodLog; +import info.novatec.inspectit.cmr.util.AgentStatusDataProvider; +import info.novatec.inspectit.spring.logger.Log; + +import java.rmi.RemoteException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * This class is used as a delegator to the real registration service. It is needed because Spring + * weaves a proxy around the real registration service which cannot be used in an RMI context with + * Java 1.4 (as no pre-generated stub is available). + * + * @author Patrice Bouillet + * + */ +@Service +public class RegistrationService implements IRegistrationService { + + /** The logger of this class. */ + @Log + Logger log; + + /** + * Should IP addresses be used for agent distinction. + */ + @Value(value = "${cmr.ipBasedAgentRegistration}") + boolean ipBasedAgentRegistration = true; + + /** + * The platform ident DAO. + */ + @Autowired + PlatformIdentDao platformIdentDao; + + /** + * The method ident DAO. + */ + @Autowired + MethodIdentDao methodIdentDao; + + /** + * The method sensor type ident DAO. + */ + @Autowired + MethodSensorTypeIdentDao methodSensorTypeIdentDao; + + /** + * The platform sensor type ident DAO. + */ + @Autowired + PlatformSensorTypeIdentDao platformSensorTypeIdentDao; + + /** + * The method ident to sensor type DAO. + */ + @Autowired + MethodIdentToSensorTypeDao methodIdentToSensorTypeDao; + + /** + * {@link AgentStatusDataProvider}. + */ + @Autowired + AgentStatusDataProvider agentStatusDataProvider; + + /** + * {@inheritDoc} + */ + @Transactional + @MethodLog + public synchronized long registerPlatformIdent(List definedIPs, String agentName, String version) throws RemoteException, ServiceException { + if (log.isInfoEnabled()) { + log.info("Trying to register Agent '" + agentName + "'"); + } + + PlatformIdent platformIdent = new PlatformIdent(); + if (ipBasedAgentRegistration) { + platformIdent.setDefinedIPs(definedIPs); + } + platformIdent.setAgentName(agentName); + + // need to reset the version number, otherwise it will be used for the query + platformIdent.setVersion(null); + + // we will not set the version for the platformIdent object here as we use this object + // for a QBE (Query by example) and this query should not be performed based on the + // version information. + + List platformIdentResults = platformIdentDao.findByExample(platformIdent); + if (1 == platformIdentResults.size()) { + platformIdent = platformIdentResults.get(0); + } else if (platformIdentResults.size() > 1) { + // this cannot occur anymore, if it occurs, then there is something totally wrong! + log.error("More than one platform ident has been retrieved! Please send your Database to the NovaTec inspectIT support!"); + throw new ServiceException("Platform ident can not be registered because the inspectIT Database has more than one corresponding platform ident already."); + } + + // always update the time stamp and ips, no matter if this is an old or new record. + platformIdent.setTimeStamp(new Timestamp(GregorianCalendar.getInstance().getTimeInMillis())); + platformIdent.setDefinedIPs(definedIPs); + + // also always update the version information of the agent + platformIdent.setVersion(version); + + platformIdentDao.saveOrUpdate(platformIdent); + + agentStatusDataProvider.registerConnected(platformIdent.getId()); + + if (log.isInfoEnabled()) { + log.info("Successfully registered Agent '" + agentName + "' with id " + platformIdent.getId() + ", version " + version + " and following network interfaces:"); + printOutDefinedIPs(definedIPs); + } + return platformIdent.getId(); + } + + /** + * {@inheritDoc} + * + * @throws ServiceException + */ + @Transactional + @MethodLog + public void unregisterPlatformIdent(List definedIPs, String agentName) throws ServiceException { + log.info("Trying to unregister the Agent with following network interfaces:"); + printOutDefinedIPs(definedIPs); + + PlatformIdent platformIdent = new PlatformIdent(); + platformIdent.setDefinedIPs(definedIPs); + platformIdent.setAgentName(agentName); + + // need to reset the version number, otherwise it will be used for the query + platformIdent.setVersion(null); + + List platformIdentResults = platformIdentDao.findByExample(platformIdent); + if (1 == platformIdentResults.size()) { + platformIdent = platformIdentResults.get(0); + agentStatusDataProvider.registerDisconnected(platformIdent.getId()); + log.info("The Agent '" + platformIdent.getAgentName() + "' has been successfully unregistered."); + } else if (platformIdentResults.size() > 1) { + // this cannot occur anymore, if it occurs, then there is something totally wrong! + log.error("More than one platform ident has been retrieved! Please send your Database to the NovaTec inspectIT support!"); + throw new ServiceException("Platform ident can not be unregistered because the inspectIT Database has more than one corresponding platform idents."); + } else { + log.warn("No registered agent with given network interfaces exists. Unregistration is aborted."); + throw new ServiceException("Platform can not be unregistered because there is no platform registered with given network interfaces."); + } + } + + /** + * {@inheritDoc} + */ + @Transactional + @MethodLog + public long registerMethodIdent(long platformId, String packageName, String className, String methodName, List parameterTypes, String returnType, int modifiers) throws RemoteException { + MethodIdent methodIdent = new MethodIdent(); + methodIdent.setPackageName(packageName); + methodIdent.setClassName(className); + methodIdent.setMethodName(methodName); + if (null == parameterTypes) { + parameterTypes = Collections.emptyList(); + } + methodIdent.setParameters(parameterTypes); + methodIdent.setReturnType(returnType); + methodIdent.setModifiers(modifiers); + + List methodIdents = methodIdentDao.findForPlatformIdent(platformId, methodIdent); + if (1 == methodIdents.size()) { + methodIdent = methodIdents.get(0); + } else { + PlatformIdent platformIdent = platformIdentDao.load(platformId); + methodIdent.setPlatformIdent(platformIdent); + } + + // always update the time stamp, no matter if this is an old or new + // record. + methodIdent.setTimeStamp(new Timestamp(GregorianCalendar.getInstance().getTimeInMillis())); + + methodIdentDao.saveOrUpdate(methodIdent); + + return methodIdent.getId(); + } + + /** + * {@inheritDoc} + */ + @Transactional + @MethodLog + public long registerMethodSensorTypeIdent(long platformId, String fullyQualifiedClassName, Map parameters) throws RemoteException { + MethodSensorTypeIdent methodSensorTypeIdent = new MethodSensorTypeIdent(); + methodSensorTypeIdent.setFullyQualifiedClassName(fullyQualifiedClassName); + + List methodSensorTypeIdents = methodSensorTypeIdentDao.findByExample(platformId, methodSensorTypeIdent); + if (1 == methodSensorTypeIdents.size()) { + methodSensorTypeIdent = methodSensorTypeIdents.get(0); + } else { + // only if the new sensor is register we need to update the platform ident + PlatformIdent platformIdent = platformIdentDao.load(platformId); + methodSensorTypeIdent.setPlatformIdent(platformIdent); + + Set sensorTypeIdents = platformIdent.getSensorTypeIdents(); + sensorTypeIdents.add(methodSensorTypeIdent); + + platformIdentDao.saveOrUpdate(platformIdent); + } + + methodSensorTypeIdent.setSettings(parameters); + + methodSensorTypeIdentDao.saveOrUpdate(methodSensorTypeIdent); + return methodSensorTypeIdent.getId(); + } + + /** + * {@inheritDoc} + */ + @Transactional + @MethodLog + public void addSensorTypeToMethod(long methodSensorTypeId, long methodId) throws RemoteException { + MethodIdentToSensorType methodIdentToSensorType = methodIdentToSensorTypeDao.find(methodId, methodSensorTypeId); + if (null == methodIdentToSensorType) { + MethodIdent methodIdent = methodIdentDao.load(methodId); + MethodSensorTypeIdent methodSensorTypeIdent = methodSensorTypeIdentDao.load(methodSensorTypeId); + methodIdentToSensorType = new MethodIdentToSensorType(methodIdent, methodSensorTypeIdent, null); + } + + // always update the timestamp + methodIdentToSensorType.setTimestamp(new Timestamp(GregorianCalendar.getInstance().getTimeInMillis())); + + methodIdentToSensorTypeDao.saveOrUpdate(methodIdentToSensorType); + } + + /** + * {@inheritDoc} + */ + @Transactional + @MethodLog + public long registerPlatformSensorTypeIdent(long platformId, String fullyQualifiedClassName) throws RemoteException { + PlatformSensorTypeIdent platformSensorTypeIdent = new PlatformSensorTypeIdent(); + platformSensorTypeIdent.setFullyQualifiedClassName(fullyQualifiedClassName); + + List platformSensorTypeIdents = platformSensorTypeIdentDao.findByExample(platformId, platformSensorTypeIdent); + PlatformIdent platformIdent; + if (1 == platformSensorTypeIdents.size()) { + platformSensorTypeIdent = platformSensorTypeIdents.get(0); + } else { + // only if it s not registered we need updating + platformIdent = platformIdentDao.load(platformId); + platformSensorTypeIdent.setPlatformIdent(platformIdent); + + Set sensorTypeIdents = platformIdent.getSensorTypeIdents(); + sensorTypeIdents.add(platformSensorTypeIdent); + + platformSensorTypeIdentDao.saveOrUpdate(platformSensorTypeIdent); + platformIdentDao.saveOrUpdate(platformIdent); + } + + return platformSensorTypeIdent.getId(); + } + + /** + * Prints out the given list of defined IP addresses. The example is: + *

+ * |- IPv4: 192.168.1.6
+ * |- IPv4: 127.0.0.1
+ * |- IPv6: fe80:0:0:0:221:5cff:fe1d:ffdf%3
+ * |- IPv6: 0:0:0:0:0:0:0:1%1 + * + * @param definedIPs + * List of IPv4 and IPv6 IPs. + */ + private void printOutDefinedIPs(List definedIPs) { + List ipList = new ArrayList(); + for (String ip : definedIPs) { + if (ip.indexOf(':') != -1) { + ipList.add("|- IPv6: " + ip); + } else { + ipList.add("|- IPv4: " + ip); + } + } + Collections.sort(ipList); + for (String ip : ipList) { + log.info(ip); + } + + } + + /** + * Is executed after dependency injection is done to perform any initialization. + */ + @PostConstruct + public void postConstruct() { + if (log.isInfoEnabled()) { + log.info("|-Registration Service active..."); + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/service/ServerStatusService.java b/CMR/src/info/novatec/inspectit/cmr/service/ServerStatusService.java new file mode 100644 index 000000000..6564b2202 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/service/ServerStatusService.java @@ -0,0 +1,87 @@ +package info.novatec.inspectit.cmr.service; + +import info.novatec.inspectit.cmr.spring.aop.MethodLog; +import info.novatec.inspectit.spring.logger.Log; +import info.novatec.inspectit.versioning.IVersioningService; + +import java.io.IOException; +import java.util.UUID; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * Implementation of the {@link IServerStatusService} interface to provide information about the + * current status of the CMR. + * + * @author Patrice Bouillet + * + */ +@Service +public class ServerStatusService implements IServerStatusService { + + /** The logger of this class. */ + @Log + Logger log; + + /** + * The status of the CMR. + */ + private ServerStatus status = ServerStatus.SERVER_STARTING; + + /** + * The versioning Service. + */ + @Autowired + private IVersioningService versioning; + + /** + * We will only log once that the version information can not be obtained, since the UI is + * checking this periodically. Even in debug level it is not wanted to be logged all the time. + */ + private boolean versionNotFoundLogged = false; + + /** + * {@inheritDoc} + */ + @MethodLog + public ServerStatus getServerStatus() { + return status; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public String getVersion() { + try { + return versioning.getVersion(); + } catch (IOException e) { + if (!versionNotFoundLogged && log.isDebugEnabled()) { + log.debug("Cannot obtain current version", e); + versionNotFoundLogged = true; + } + return IServerStatusService.VERSION_NOT_AVAILABLE; + } + } + + /** + * Is executed after dependency injection is done to perform any initialization. + * + * @throws Exception + * if an error occurs during {@link PostConstruct} + */ + @PostConstruct + public void postConstruct() throws Exception { + status = ServerStatus.SERVER_ONLINE; + status.setRegistrationIdsValidationKey(UUID.randomUUID().toString()); + + if (log.isInfoEnabled()) { + log.info("|-Server Status Service active..."); + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/service/SqlDataAccessService.java b/CMR/src/info/novatec/inspectit/cmr/service/SqlDataAccessService.java new file mode 100644 index 000000000..26318b4aa --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/service/SqlDataAccessService.java @@ -0,0 +1,83 @@ +package info.novatec.inspectit.cmr.service; + +import info.novatec.inspectit.cmr.dao.SqlDataDao; +import info.novatec.inspectit.cmr.spring.aop.MethodLog; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.spring.logger.Log; + +import java.util.Date; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author Patrice Bouillet + * + */ +@Service +public class SqlDataAccessService implements ISqlDataAccessService { + + /** The logger of this class. */ + @Log + Logger log; + + /** + * The sql DAO. + */ + @Autowired + private SqlDataDao sqlDataDao; + + /** + * {@inheritDoc} + */ + @MethodLog + public List getAggregatedSqlStatements(SqlStatementData sqlStatementData) { + List result = sqlDataDao.getAggregatedSqlStatements(sqlStatementData); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getAggregatedSqlStatements(SqlStatementData sqlStatementData, Date fromDate, Date toDate) { + List result = sqlDataDao.getAggregatedSqlStatements(sqlStatementData, fromDate, toDate); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getParameterAggregatedSqlStatements(SqlStatementData sqlStatementData) { + List result = sqlDataDao.getParameterAggregatedSqlStatements(sqlStatementData); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getParameterAggregatedSqlStatements(SqlStatementData sqlStatementData, Date fromDate, Date toDate) { + List result = sqlDataDao.getParameterAggregatedSqlStatements(sqlStatementData, fromDate, toDate); + return result; + } + + /** + * Is executed after dependency injection is done to perform any initialization. + * + * @throws Exception + * if an error occurs during {@link PostConstruct} + */ + @PostConstruct + public void postConstruct() throws Exception { + if (log.isInfoEnabled()) { + log.info("|-SQL Data Access Service active..."); + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/service/StorageService.java b/CMR/src/info/novatec/inspectit/cmr/service/StorageService.java new file mode 100644 index 000000000..3809cfc0a --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/service/StorageService.java @@ -0,0 +1,591 @@ +package info.novatec.inspectit.cmr.service; + +import info.novatec.inspectit.cmr.dao.StorageDataDao; +import info.novatec.inspectit.cmr.spring.aop.MethodLog; +import info.novatec.inspectit.cmr.storage.CmrStorageManager; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.cmr.RecordingData; +import info.novatec.inspectit.spring.logger.Log; +import info.novatec.inspectit.storage.IStorageData; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.StorageFileType; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.label.management.AbstractLabelManagementAction; +import info.novatec.inspectit.storage.label.type.AbstractStorageLabelType; +import info.novatec.inspectit.storage.processor.AbstractDataProcessor; +import info.novatec.inspectit.storage.recording.RecordingProperties; +import info.novatec.inspectit.storage.recording.RecordingState; +import info.novatec.inspectit.storage.serializer.SerializationException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * Storage service implementation. + * + * @author Ivan Senic + * + */ +@Service +public class StorageService implements IStorageService { + + /** The logger of this class. */ + @Log + Logger log; + + /** + * Storage manager. + */ + @Autowired + private CmrStorageManager storageManager; + + /** + * Label data DAO. + */ + @Autowired + private StorageDataDao storageLabelDataDao; + + /** + * Creates the new storage on the CMR with information given in {@link StorageData} object. + * + * @param storageData + * Information about new storage. + * @throws StorageException + * When storage creation fails. + */ + @MethodLog + public void createStorage(StorageData storageData) throws StorageException { + try { + storageManager.createStorage(storageData); + } catch (Exception e) { + throw new StorageException("Exception occurred trying to create storage" + storageData + ".", e); + } + } + + /** + * Opens an already existing storage in means that it prepares it for write. + * + * @param storageData + * Storage to open. + * @throws StorageException + * When storage with provided {@link StorageData} does not exists. When storage + * opening fails. + */ + @MethodLog + public void openStorage(StorageData storageData) throws StorageException { + if (!storageManager.isStorageExisting(storageData)) { + throw new StorageException("The storage " + storageData + " does not exsist on the CMR."); + } + try { + storageManager.openStorage(storageData); + } catch (Exception e) { + throw new StorageException("Exception occurred trying to open storage" + storageData + ".", e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public StorageData createAndOpenStorage(StorageData storageData) throws StorageException { + this.createStorage(storageData); + this.openStorage(storageData); + return storageData; + } + + /** + * {@inheritDoc} + * + * @throws StorageException + */ + @MethodLog + public void closeStorage(StorageData storageData) throws StorageException { + try { + storageManager.closeStorage(storageData); + } catch (IOException | SerializationException e) { + throw new StorageException("Exception occurred trying to close storage" + storageData + ".", e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public void deleteStorage(StorageData storageData) throws StorageException { + try { + storageManager.deleteStorage(storageData); + } catch (IOException e) { + throw new StorageException("Exception occurred trying to delete storage" + storageData + ".", e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public boolean isStorageOpen(StorageData storageData) { + return storageManager.isStorageOpen(storageData); + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getOpenedStorages() { + return storageManager.getOpenedStorages(); + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getExistingStorages() { + return storageManager.getExistingStorages(); + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getReadableStorages() { + return storageManager.getReadableStorages(); + } + + /** + * {@inheritDoc} + */ + @MethodLog + public RecordingState getRecordingState() { + return storageManager.getRecordingState(); + } + + /** + * {@inheritDoc} + */ + @MethodLog + public StorageData startOrScheduleRecording(StorageData storageData, RecordingProperties recordingProperties) throws StorageException { + if ((storageManager.getRecordingState() == RecordingState.ON || storageManager.getRecordingState() == RecordingState.SCHEDULED) && !storageData.equals(storageManager.getRecordingStorage())) { + throw new StorageException("Recording is already active/scheduled with different storage. Only one storage at time can be chosen as recording destination."); + } else if (storageManager.getRecordingState() == RecordingState.ON || storageManager.getRecordingState() == RecordingState.SCHEDULED) { + throw new StorageException("Recording is already active/scheduled with selected storage."); + } else { + try { + storageManager.startOrScheduleRecording(storageData, recordingProperties); + return storageManager.getRecordingStorage(); + } catch (IOException | SerializationException e) { + throw new StorageException("Exception occurred trying to start recording on storage " + storageData + ".", e); + } + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public void stopRecording() throws StorageException { + try { + storageManager.stopRecording(); + } catch (Exception e) { + throw new StorageException("Exception occurred trying to stop recording.", e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + @Override + public RecordingData getRecordingData() { + if (storageManager.getRecordingState() == RecordingState.ON || storageManager.getRecordingState() == RecordingState.SCHEDULED) { + RecordingData recordingData = new RecordingData(); + RecordingProperties recordingProperties = storageManager.getRecordingProperties(); + if (null != recordingProperties) { + recordingData.setRecordStartDate(recordingProperties.getRecordStartDate()); + recordingData.setRecordEndDate(recordingProperties.getRecordEndDate()); + } + recordingData.setRecordingStorage(storageManager.getRecordingStorage()); + recordingData.setRecordingWritingStatus(storageManager.getRecordingStatus()); + return recordingData; + } else { + return null; + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public void writeToStorage(StorageData storageData, Collection defaultDataCollection, Collection dataProcessors, boolean synchronously) throws StorageException { + if (!storageManager.isStorageOpen(storageData)) { + throw new StorageException("Writing to storage tried to be performed on the storage that is not opened. Please open the storage first."); + } + try { + storageManager.writeToStorage(storageData, defaultDataCollection, dataProcessors, synchronously); + } catch (IOException | SerializationException e) { + throw new StorageException("Copy Buffer to Storage action encountered an error.", e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public StorageData copyBufferToStorage(StorageData storageData, List platformIdents, Collection dataProcessors, boolean autoFinalize) throws StorageException { + try { + storageManager.copyBufferToStorage(storageData, platformIdents, dataProcessors, autoFinalize); + return storageData; + } catch (IOException | SerializationException e) { + throw new StorageException("Copy Buffer to Storage action encountered an error.", e); + } + } + + @Override + public StorageData copyDataToStorage(StorageData storageData, Collection elementIds, long platformIdent, Collection dataProcessors, boolean autoFinalize) + throws StorageException { + try { + storageManager.copyDataToStorage(storageData, elementIds, platformIdent, dataProcessors, autoFinalize); + return storageData; + } catch (IOException | SerializationException e) { + throw new StorageException("Copy Data to Storage action encountered an error.", e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public Map getIndexFilesLocations(StorageData storageData) throws StorageException { + if (!storageManager.isStorageExisting(storageData)) { + throw new StorageException("The storage " + storageData + " does not exist on the CMR."); + } + try { + return storageManager.getFilesHttpLocation(storageData, StorageFileType.INDEX_FILE.getExtension()); + } catch (IOException e) { + throw new StorageException("Exception occurred trying to load storage index files locations.", e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public Map getDataFilesLocations(StorageData storageData) throws StorageException { + if (!storageManager.isStorageExisting(storageData)) { + throw new StorageException("The storage " + storageData + " does not exist on the CMR."); + } + try { + return storageManager.getFilesHttpLocation(storageData, StorageFileType.DATA_FILE.getExtension()); + } catch (IOException e) { + throw new StorageException("Exception occurred trying to load storage data files locations.", e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public Map getCachedDataFilesLocations(StorageData storageData) throws StorageException { + if (!storageManager.isStorageExisting(storageData)) { + throw new StorageException("The storage " + storageData + " does not exist on the CMR."); + } + try { + return storageManager.getFilesHttpLocation(storageData, StorageFileType.CACHED_DATA_FILE.getExtension()); + } catch (IOException e) { + throw new StorageException("Exception occurred trying to load storage cached data files locations.", e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public Map getAgentFilesLocations(StorageData storageData) throws StorageException { + if (!storageManager.isStorageExisting(storageData)) { + throw new StorageException("The storage " + storageData + " does not exist on the CMR."); + } + try { + return storageManager.getFilesHttpLocation(storageData, StorageFileType.AGENT_FILE.getExtension()); + } catch (IOException e) { + throw new StorageException("Exception occurred trying to load storage agent files locations.", e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public StorageData addLabelToStorage(StorageData storageData, AbstractStorageLabel storageLabel, boolean doOverwrite) throws StorageException { + try { + storageManager.addLabelToStorage(storageData, storageLabel, doOverwrite); + storageLabelDataDao.saveLabel(storageLabel); + return storageManager.getStorageData(storageData.getId()); + } catch (IOException | SerializationException e) { + throw new StorageException("Exception occurred trying to save storage data changes to disk.", e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public StorageData addLabelsToStorage(StorageData storageData, Collection> storageLabels, boolean doOverwrite) throws StorageException { + try { + for (AbstractStorageLabel storageLabel : storageLabels) { + storageManager.addLabelToStorage(storageData, storageLabel, doOverwrite); + storageLabelDataDao.saveLabel(storageLabel); + } + return storageManager.getStorageData(storageData.getId()); + } catch (IOException | SerializationException e) { + throw new StorageException("Exception occurred trying to save storage data changes to disk.", e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public StorageData removeLabelFromStorage(StorageData storageData, AbstractStorageLabel storageLabel) throws StorageException { + try { + storageManager.removeLabelFromStorage(storageData, storageLabel); + return storageManager.getStorageData(storageData.getId()); + } catch (IOException | SerializationException e) { + throw new StorageException("Exception occurred trying to save storage data changes to disk.", e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public StorageData removeLabelsFromStorage(StorageData storageData, List> storageLabelList) throws StorageException { + try { + for (AbstractStorageLabel label : storageLabelList) { + storageManager.removeLabelFromStorage(storageData, label); + } + return storageManager.getStorageData(storageData.getId()); + } catch (IOException | SerializationException e) { + throw new StorageException("Exception occurred trying to save storage data changes to disk.", e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public void executeLabelManagementActions(Collection managementActions) throws StorageException { + for (AbstractLabelManagementAction managementAction : managementActions) { + managementAction.execute(this); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public Collection> getAllLabelsInStorages() { + Set> labels = new HashSet>(); + for (StorageData storageData : getExistingStorages()) { + labels.addAll(storageData.getLabelList()); + } + return labels; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List> getAllLabels() { + return storageLabelDataDao.getAllLabels(); + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List> getLabelSuggestions(AbstractStorageLabelType labeltype) { + List> results = storageLabelDataDao.getAllLabelsForType(labeltype); + return results; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public void saveLabelToCmr(AbstractStorageLabel storageLabel) { + storageLabelDataDao.saveLabel(storageLabel); + } + + /** + * {@inheritDoc} + */ + @MethodLog + public void saveLabelsToCmr(Collection> storageLabels) { + for (AbstractStorageLabel label : storageLabels) { + storageLabelDataDao.saveLabel(label); + } + } + + /** + * {@inheritDoc} + * + */ + @MethodLog + public void removeLabelFromCmr(AbstractStorageLabel storageLabel, boolean removeFromStoragesAlso) throws StorageException { + storageLabelDataDao.removeLabel(storageLabel); + if (removeFromStoragesAlso) { + for (StorageData storageData : getExistingStorages()) { + removeLabelFromStorage(storageData, storageLabel); + } + } + } + + /** + * {@inheritDoc} + * + */ + @MethodLog + public void removeLabelsFromCmr(Collection> storageLabels, boolean removeFromStoragesAlso) throws StorageException { + storageLabelDataDao.removeLabels(storageLabels); + if (removeFromStoragesAlso) { + for (StorageData storageData : getExistingStorages()) { + removeLabelsFromStorage(storageData, new ArrayList>(storageLabels)); + } + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public void saveLabelType(AbstractStorageLabelType labelType) { + storageLabelDataDao.saveLabelType(labelType); + } + + /** + * {@inheritDoc} + */ + @MethodLog + public void removeLabelType(AbstractStorageLabelType labelType) throws StorageException { + try { + storageLabelDataDao.removeLabelType(labelType); + } catch (Exception e) { + throw new StorageException("Label type was not removed", e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public > List getLabelTypes(Class labelTypeClass) { + return storageLabelDataDao.getLabelTypes(labelTypeClass); + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List> getAllLabelTypes() { + return storageLabelDataDao.getAllLabelTypes(); + } + + /** + * {@inheritDoc} + */ + @MethodLog + public void updateStorageData(StorageData storageData) throws StorageException { + try { + storageManager.updateStorageData(storageData); + } catch (IOException | SerializationException e) { + throw new StorageException(e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public long getStorageQueuedWriteTaskCount(StorageData storageData) { + return storageManager.getStorageQueuedWriteTaskCount(storageData); + } + + /** + * {@inheritDoc} + */ + @MethodLog + public void unpackUploadedStorage(IStorageData storageData) throws StorageException { + try { + storageManager.unpackUploadedStorage(storageData); + } catch (IOException e) { + throw new StorageException("Exception occurred trying to check for imported storage.", e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public void createStorageFromUploadedDir(final IStorageData localStorageData) throws StorageException { + try { + storageManager.createStorageFromUploadedDir(localStorageData); + } catch (IOException | SerializationException e) { + throw new StorageException("Exception occurred trying to create storage from uploaded local storage.", e); + } + } + + /** + * {@inheritDoc} + */ + @MethodLog + public void cacheStorageData(StorageData storageData, Collection data, int hash) throws StorageException { + if (!storageManager.isStorageExisting(storageData)) { + throw new StorageException("The storage " + storageData + " does not exist on the CMR. Data caching is not possible."); + } + if (!storageManager.isStorageClosed(storageData)) { + throw new StorageException("The storage " + storageData + " is still not finalized. Data caching is not possible."); + } + try { + storageManager.cacheStorageData(storageData, data, hash); + } catch (IOException | SerializationException e) { + throw new StorageException("Exception occurred trying to cache data fpr storage.", e); + } + + } + + /** + * {@inheritDoc} + */ + @MethodLog + public String getCachedStorageDataFileLocation(StorageData storageData, int hash) throws StorageException { + if (!storageManager.isStorageExisting(storageData)) { + throw new StorageException("The storage " + storageData + " does not exist on the CMR. Cached data file can not be resolved."); + } + return storageManager.getCachedStorageDataFileLocation(storageData, hash); + } + + /** + * Is executed after dependency injection is done to perform any initialization. + * + * @throws Exception + * if an error occurs during {@link PostConstruct} + */ + @PostConstruct + public void postConstruct() throws Exception { + if (log.isInfoEnabled()) { + log.info("|-Storage Service active..."); + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/service/TimerDataAccessService.java b/CMR/src/info/novatec/inspectit/cmr/service/TimerDataAccessService.java new file mode 100644 index 000000000..41822254a --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/service/TimerDataAccessService.java @@ -0,0 +1,46 @@ +package info.novatec.inspectit.cmr.service; + +import info.novatec.inspectit.cmr.dao.TimerDataDao; +import info.novatec.inspectit.cmr.spring.aop.MethodLog; +import info.novatec.inspectit.communication.data.TimerData; + +import java.util.Date; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * Timer data service. + * + * @author Ivan Senic + * + */ +@Service +public class TimerDataAccessService implements ITimerDataAccessService { + + /** + * Timer data dao. + */ + @Autowired + private TimerDataDao timerDataDao; + + /** + * {@inheritDoc} + */ + @MethodLog + public List getAggregatedTimerData(TimerData timerData) { + List result = timerDataDao.getAggregatedTimerData(timerData); + return result; + } + + /** + * {@inheritDoc} + */ + @MethodLog + public List getAggregatedTimerData(TimerData timerData, Date fromDate, Date toDate) { + List result = timerDataDao.getAggregatedTimerData(timerData, fromDate, toDate); + return result; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/service/rest/CmrRestfulService.java b/CMR/src/info/novatec/inspectit/cmr/service/rest/CmrRestfulService.java new file mode 100644 index 000000000..10b8b5707 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/service/rest/CmrRestfulService.java @@ -0,0 +1,81 @@ +package info.novatec.inspectit.cmr.service.rest; + +import info.novatec.inspectit.cmr.service.ICmrManagementService; +import info.novatec.inspectit.cmr.service.IServerStatusService; +import info.novatec.inspectit.cmr.service.rest.error.JsonError; +import info.novatec.inspectit.communication.data.cmr.CmrStatusData; +import info.novatec.inspectit.versioning.IVersioningService; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.ModelAndView; + +/** + * Restful service provider for CMR information. + * + * @author Ivan Senic + * + */ +@Controller +@RequestMapping(value = "/cmr") +public class CmrRestfulService { + + /** + * Reference to the existing {@link IVersioningService}. + */ + @Autowired + private IServerStatusService versioningService; + + /** + * Reference to the existing {@link IVersioningService}. + */ + @Autowired + private ICmrManagementService cmrManagementService; + + /** + * Handling of all the exceptions happening in this controller. + * + * @param exception + * Exception being thrown + * @return {@link ModelAndView} + */ + @ExceptionHandler(Exception.class) + public ModelAndView handleAllException(Exception exception) { + return new JsonError(exception).asModelAndView(); + } + + /** + * Returns CMR version. + *

+ * Example URL: /cmr/version + * + * @return Returns CMR version. + * @throws IOException + * If version information is not available. + */ + @RequestMapping(method = RequestMethod.GET, value = "version") + @ResponseBody + public String getVersion() throws IOException { + return versioningService.getVersion(); + } + + /** + * Returns CMR status information. + *

+ * Example URL: /cmr/status-data + * + * @return Returns CMR status information. + */ + @RequestMapping(method = RequestMethod.GET, value = "status-data") + @ResponseBody + public CmrStatusData getStatusData() { + return cmrManagementService.getCmrStatusData(); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/service/rest/StorageRestfulService.java b/CMR/src/info/novatec/inspectit/cmr/service/rest/StorageRestfulService.java new file mode 100644 index 000000000..a76f64820 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/service/rest/StorageRestfulService.java @@ -0,0 +1,327 @@ +package info.novatec.inspectit.cmr.service.rest; + +import info.novatec.inspectit.cmr.service.IStorageService; +import info.novatec.inspectit.cmr.service.rest.error.JsonError; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.ClassLoadingInformationData; +import info.novatec.inspectit.communication.data.CpuInformationData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.MemoryInformationData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.SystemInformationData; +import info.novatec.inspectit.communication.data.ThreadInformationData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.communication.data.cmr.RecordingData; +import info.novatec.inspectit.indexing.aggregation.impl.SqlStatementDataAggregator; +import info.novatec.inspectit.indexing.aggregation.impl.TimerDataAggregator; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.processor.AbstractDataProcessor; +import info.novatec.inspectit.storage.processor.impl.DataAggregatorProcessor; +import info.novatec.inspectit.storage.processor.impl.DataSaverProcessor; +import info.novatec.inspectit.storage.processor.impl.InvocationClonerDataProcessor; +import info.novatec.inspectit.storage.processor.impl.InvocationExtractorDataProcessor; +import info.novatec.inspectit.storage.recording.RecordingProperties; +import info.novatec.inspectit.storage.recording.RecordingState; + +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.ModelAndView; + +/** + * Restful service provider for storages. + * + * @author Ivan Senic + * + */ +@Controller +@RequestMapping(value = "/storage") +public class StorageRestfulService { + + /** + * Reference to the existing {@link IStorageService}. + */ + @Autowired + IStorageService storageService; + + /** + * Handling of all the exceptions happening in this controller. + * + * @param exception + * Exception being thrown + * @return {@link ModelAndView} + */ + @ExceptionHandler(Exception.class) + public ModelAndView handleAllException(Exception exception) { + return new JsonError(exception).asModelAndView(); + } + + /** + * Returns all storages. + *

+ * Example URL: /storage/all + * + * @return List of all storages. + */ + @RequestMapping(method = RequestMethod.GET, value = "all") + @ResponseBody + public List getAllStorages() { + List storages = storageService.getExistingStorages(); + return storages; + } + + /** + * Returns storage by ID. + *

+ * Example URL: /storage/get?id=1 + * + * @param id + * ID bounded from path. + * @return One storage or null if the storage with given ID does not exists. + */ + @RequestMapping(method = RequestMethod.GET, value = "get") + @ResponseBody + public StorageData getStorageById(@RequestParam(value = "id", required = true) String id) { + List storages = storageService.getExistingStorages(); + for (StorageData storageData : storages) { + if (Objects.equals(id, storageData.getId())) { + return storageData; + } + } + return null; + } + + /** + * Creates a new storage with given name. + *

+ * Example URL: /storage/create?name=ViaRest + * + * @param name + * Name of the storage. + * @return Map containing message and created storage. + * @throws StorageException + * If {@link StorageException} occurs. + */ + @RequestMapping(method = RequestMethod.GET, value = "create") + @ResponseBody + public Object createStorage(@RequestParam(value = "name", required = true) String name) throws StorageException { + if (StringUtils.isEmpty(name)) { + throw new StorageException("Can not create the storage when name is not provided."); + } + + StorageData storageData = new StorageData(); + storageData.setName(name); + storageData = storageService.createAndOpenStorage(storageData); + + Map resultMap = new HashMap<>(); + resultMap.put("message", "Storage successfully created."); + resultMap.put("storage", storageData); + return resultMap; + } + + /** + * Finalize storage by ID. + *

+ * Example URL: /storage/finalize?id=1 + * + * @param id + * ID bounded from path. + * @throws StorageException + * If {@link StorageException} occurs. + * @return Message for the user. + */ + @RequestMapping(method = RequestMethod.GET, value = "finalize") + @ResponseBody + public Object finalizeStorage(@RequestParam(value = "id", required = true) String id) throws StorageException { + StorageData storageData = new StorageData(); + storageData.setId(id); + storageService.closeStorage(storageData); + return Collections.singletonMap("message", "Storage id " + id + " successfully finalized."); + } + + /** + * Deletes storage by ID. + *

+ * Example URL: /storage/delete?id=1 + * + * @param id + * ID bounded from path. + * @throws StorageException + * If {@link StorageException} occurs. + * @return Message for the user. + */ + @RequestMapping(method = RequestMethod.GET, value = "delete") + @ResponseBody + public Object deleteStorage(@RequestParam(value = "id", required = true) String id) throws StorageException { + StorageData storageData = new StorageData(); + storageData.setId(id); + storageService.deleteStorage(storageData); + return Collections.singletonMap("message", "Storage id " + id + " successfully deleted."); + } + + /** + * Returns the current state of the recording. + *

+ * Example URL: /storage/recording-state + * + * @return {@link RecordingState}. + */ + @RequestMapping(method = RequestMethod.GET, value = "recording-state") + @ResponseBody + public Map getRecordingState() { + RecordingState state = storageService.getRecordingState(); + Map resultMap = new HashMap<>(); + resultMap.put("recordingState", state); + if (RecordingState.OFF != state) { + // add recording storage + RecordingData recordingData = storageService.getRecordingData(); + resultMap.put("recordingStorage", recordingData.getRecordingStorage()); + + if (RecordingState.ON == state) { + // add end date if we are having one + Date recordingEndDate = recordingData.getRecordEndDate(); + if (null != recordingEndDate) { + resultMap.put("recordingStopDate", DateFormat.getDateTimeInstance().format(recordingEndDate)); + } + } + + // add start date if it's scheduled + if (RecordingState.SCHEDULED == state) { + resultMap.put("schduledStartDate", DateFormat.getDateTimeInstance().format(recordingData.getRecordStartDate())); + } + + } + return resultMap; + } + + /** + * Stops recording. + *

+ * Example URL: /storage/stop-recording + * + * @throws StorageException + * If {@link StorageException} occurs. + * @return Message for the user. + */ + @RequestMapping(method = RequestMethod.GET, value = "stop-recording") + @ResponseBody + public Object stopRecording() throws StorageException { + storageService.stopRecording(); + return Collections.singletonMap("message", "Recording stopped."); + } + + /** + * Starts recording on the storage with the given ID with some advanced settings. This method + * allows specification of the start delay and recording duration in milliseconds. Zero values + * for that parameters are omitting them. + *

+ * Example URL: /storage/start-recording/?id=1&startDelay=30000&recordingDuration=60000 + * (makes a 30s delay and records for 60s) + * + * @param id + * Storage ID. + * @param startDelay + * startDelay in milliseconds or 0 to ignore + * @param recordingDuration + * recording duration in milliseconds or 0 to ignore + * @param extractInvocations + * If invocations should be extracted. + * @param autoFinalize + * If storage should be auto-finalized when recording is stopped. + * @return Map with informations for the user. + * @throws StorageException + * If {@link StorageException} occurs. + */ + @RequestMapping(method = RequestMethod.GET, value = "start-recording") + @ResponseBody + public Object startOrScheduleRecording(@RequestParam(value = "id", required = true) String id, @RequestParam(value = "startDelay", required = false) Long startDelay, + @RequestParam(value = "recordingDuration", required = false) Long recordingDuration, + @RequestParam(value = "extractInvocations", required = false, defaultValue = "true") Boolean extractInvocations, + @RequestParam(value = "autoFinalize", required = false, defaultValue = "true") Boolean autoFinalize) throws StorageException { + if (null == getStorageById(id)) { + throw new StorageException("Storage with id " + id + " that is chosen for recording does not exists."); + } + + StorageData storageData = new StorageData(); + storageData.setId(id); + + // extractInvocations and autoFinalize have default Values and should never be null + RecordingProperties recordingProperties = getRecordingProperties(extractInvocations.booleanValue()); + recordingProperties.setAutoFinalize(autoFinalize.booleanValue()); + + if (null != startDelay && startDelay.longValue() > 0) { + recordingProperties.setStartDelay(startDelay.longValue()); + } + + if (null != recordingDuration && recordingDuration.longValue() > 0) { + recordingProperties.setRecordDuration(recordingDuration.longValue()); + } + + StorageData recordingStorage = storageService.startOrScheduleRecording(storageData, recordingProperties); + Map resultMap = new HashMap<>(); + if (recordingProperties.getStartDelay() > 0) { + resultMap.put("message", "Recording scheduled."); + } else { + resultMap.put("message", "Recording started."); + } + resultMap.put("recordingStorage", recordingStorage); + return resultMap; + + } + + /** + * Returns the recording properties with correctly set default set of + * {@link AbstractDataProcessor}s. + * + * @param extractInvocations + * If invocations should be extracted. + * @return {@link RecordingProperties}. + */ + private RecordingProperties getRecordingProperties(boolean extractInvocations) { + RecordingProperties recordingProperties = new RecordingProperties(); + + List normalProcessors = new ArrayList(); + + // data saver + List> classesToSave = new ArrayList>(); + Collections.addAll(classesToSave, InvocationSequenceData.class, HttpTimerData.class, ExceptionSensorData.class, MemoryInformationData.class, CpuInformationData.class, + ClassLoadingInformationData.class, ThreadInformationData.class, SystemInformationData.class); + DataSaverProcessor dataSaverProcessor = new DataSaverProcessor(classesToSave, true); + normalProcessors.add(dataSaverProcessor); + + // data aggregators + normalProcessors.add(new DataAggregatorProcessor(TimerData.class, 5000, new TimerDataAggregator(), true)); + normalProcessors.add(new DataAggregatorProcessor(SqlStatementData.class, 5000, new SqlStatementDataAggregator(true), true)); + + // invocations support + if (extractInvocations) { + List chainedProcessorsForExtractor = new ArrayList(); + chainedProcessorsForExtractor.addAll(normalProcessors); + InvocationExtractorDataProcessor invocationExtractorDataProcessor = new InvocationExtractorDataProcessor(chainedProcessorsForExtractor); + normalProcessors.add(invocationExtractorDataProcessor); + } + normalProcessors.add(new InvocationClonerDataProcessor()); + + recordingProperties.setRecordingDataProcessors(normalProcessors); + + return recordingProperties; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/service/rest/error/JsonError.java b/CMR/src/info/novatec/inspectit/cmr/service/rest/error/JsonError.java new file mode 100644 index 000000000..40df3a34d --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/service/rest/error/JsonError.java @@ -0,0 +1,43 @@ +package info.novatec.inspectit.cmr.service.rest.error; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.view.json.MappingJacksonJsonView; + +/** + * Class for displaying exceptions in RESTful services with the {@link ModelAndView}. + * + * @author Ivan Senic + * + */ +public class JsonError { + + /** + * Cause of the error. + */ + private final Exception exception; + + /** + * Constructor. + * + * @param exception + * Cause of the error. + */ + public JsonError(Exception exception) { + this.exception = exception; + } + + /** + * @return Returns {@link ModelAndView} created from {@link MappingJacksonJsonView} holding the + * information about the exception. + */ + public ModelAndView asModelAndView() { + MappingJacksonJsonView jsonView = new MappingJacksonJsonView(); + Map map = new HashMap<>(); + map.put("error", exception.getMessage()); + map.put("exceptionType", exception.getClass().getName()); + return new ModelAndView(jsonView, map); + } +} diff --git a/CMR/src/info/novatec/inspectit/cmr/spring/aop/ExceptionInterceptor.java b/CMR/src/info/novatec/inspectit/cmr/spring/aop/ExceptionInterceptor.java new file mode 100644 index 000000000..3219fd74e --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/spring/aop/ExceptionInterceptor.java @@ -0,0 +1,44 @@ +package info.novatec.inspectit.cmr.spring.aop; + +import info.novatec.inspectit.spring.logger.Log; + +import java.util.Arrays; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.slf4j.Logger; +import org.springframework.stereotype.Component; + +/** + * Aspect that defines the after throwing advice that is bounded to all methods of all classes in + * the info.novatec.inspectit.cmr.service package. The advice prints the exception if one is raised + * and provides additional information like what method was executed, what parameters where passed, + * etc. + * + * @author Ivan Senic + * + */ +@Aspect +@Component +public class ExceptionInterceptor { + + /** + * Logger for logging purposes. + */ + @Log + Logger log; + + /** + * The advice. + * + * @param jp + * {@link JoinPoint} holding all information about pointcut. + * @param exception + * Exception being thrown. + */ + @AfterThrowing(pointcut = "execution(* info.novatec.inspectit.cmr.service.*.*(..))", throwing = "exception") + public void logServiceException(JoinPoint jp, Exception exception) { + log.warn("Exception thrown in the service method " + jp.getSignature() + " executed with following parameters: " + Arrays.toString(jp.getArgs()) + ".", exception); + } +} diff --git a/CMR/src/info/novatec/inspectit/cmr/spring/aop/MethodLog.java b/CMR/src/info/novatec/inspectit/cmr/spring/aop/MethodLog.java new file mode 100644 index 000000000..8255ba626 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/spring/aop/MethodLog.java @@ -0,0 +1,48 @@ +package info.novatec.inspectit.cmr.spring.aop; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker for methods that will be logged and/or profiled, By placing this annotation on a method + * spring will proxy the service and call the interceptor that provides advice to the real method + * call. + * + * @author Patrice Bouillet + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface MethodLog { + + /** + * The log level which can be used. The level from logback cannot be used directly as it is not + * allowed as a return type. + * + * @author Patrice Bouillet + * + */ + public enum Level { + OFF, ERROR, WARN, INFO, DEBUG, TRACE, ALL; // NOCHK + } + + /** + * The defined level on which the time messages shall be printed to. + */ + Level timeLogLevel() default Level.DEBUG; + + /** + * The defined level on which the trace messages shall be printed to. + */ + Level traceLogLevel() default Level.TRACE; + + /** + * Defines a duration limit on this method. If the methods duration exceed the specified one, a + * message will be printed into the log. + */ + long durationLimit() default -1; + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/spring/aop/MethodLogInterceptor.java b/CMR/src/info/novatec/inspectit/cmr/spring/aop/MethodLogInterceptor.java new file mode 100644 index 000000000..612660209 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/spring/aop/MethodLogInterceptor.java @@ -0,0 +1,176 @@ +package info.novatec.inspectit.cmr.spring.aop; + +import info.novatec.inspectit.cmr.util.Converter; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.LoggerFactory; +import org.slf4j.spi.LocationAwareLogger; +import org.springframework.stereotype.Component; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; + +/** + * The logging interceptor which will be active for each method being annotated with @ + * {@link MethodLog}. + * + * @author Patrice Bouillet + * + */ +@Aspect +@Component +public class MethodLogInterceptor { + + /** + * The message printed in the log if the specified duration has been exceeded. + */ + private static final String DURATION_THRESHOLD_MSG = "WARNING: Duration threshold (%s ms) exceeded for method '{}': {} ms"; + + /** + * The message printed in the log if the specified duration has been exceeded. + */ + private static final String DURATION_THRESHOLD_MSG_W_TRACE = " WARNING: Duration threshold ({} ms) exceeded for method '{}': {} ms"; + + /** + * The log format for the printing of the method duration. + */ + private static final String TIME_LOG_FORMAT = "'{}' duration: {} ms"; + + /** + * The log format for the printing of the method duration if the trace level is active, too. + */ + private static final String TIME_LOG_FORMAT_W_TRACE = " '{}' duration: {} ms"; + + /** + * The enter format String for the trace level. + */ + private static final String TRACE_ENTER_FORMAT = "--> {}#{}()"; + + /** + * The exit format String for the trace level. + */ + private static final String TRACE_EXIT_FORMAT = "<-- {}#{}()"; + + /** + * The regular expression to split the method names. + */ + private static final String SPLIT_METHOD_REGEX = "(?=\\p{Lu})"; + + /** + * The pre-compiled Pattern object out of the defined regular expression. + */ + private static final Pattern SPLIT_METHOD_PATTERN = Pattern.compile(SPLIT_METHOD_REGEX); + + /** + * This map holds the mapping between the log levels defined in the aop log and the logback log + * level. Please look at class {@link MethodLog} for the reason for this. + */ + private static final Map LEVELS = new HashMap(8, 1.0f); + + static { + // initialize all the available levels + LEVELS.put(MethodLog.Level.ALL, Level.ALL); + LEVELS.put(MethodLog.Level.DEBUG, Level.DEBUG); + LEVELS.put(MethodLog.Level.ERROR, Level.ERROR); + LEVELS.put(MethodLog.Level.INFO, Level.INFO); + LEVELS.put(MethodLog.Level.OFF, Level.OFF); + LEVELS.put(MethodLog.Level.TRACE, Level.TRACE); + LEVELS.put(MethodLog.Level.WARN, Level.WARN); + } + + /** + * Advice around the method that are annotated with {@link MethodLog} that processes wanted + * logging if needed. + * + * @param joinPoint + * {@link ProceedingJoinPoint}. + * @param methodLog + * {@link MethodLog} on the method. + * @return Returns result of method invocation. + * @throws Throwable + * If {@link Throwable} is result of method invocation. + */ + @Around("@annotation(info.novatec.inspectit.cmr.spring.aop.MethodLog) && @annotation(methodLog)") + public Object doMethodLog(ProceedingJoinPoint joinPoint, MethodLog methodLog) throws Throwable { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Logger logger = (Logger) LoggerFactory.getLogger(joinPoint.getTarget().getClass()); + Level timeLogLevel = LEVELS.get(methodLog.timeLogLevel()); + Level traceLogLevel = LEVELS.get(methodLog.traceLogLevel()); + + if (logger.isEnabledFor(traceLogLevel)) { + logger.log(null, signature.getDeclaringType().getName(), LocationAwareLogger.TRACE_INT, TRACE_ENTER_FORMAT, new Object[] { signature.getDeclaringType().getName(), signature.getName() }, + null); + } + + long startTime = System.nanoTime(); + Object object = joinPoint.proceed(); + long endTime = System.nanoTime(); + double duration = Converter.nanoToMilliseconds(endTime - startTime); + + String methodName = null; + if (logger.isEnabledFor(timeLogLevel)) { + methodName = convertMethodName(signature.getName()); + String formatString; + if (logger.isEnabledFor(traceLogLevel)) { + formatString = TIME_LOG_FORMAT_W_TRACE; + } else { + formatString = TIME_LOG_FORMAT; + } + logger.log(null, signature.getDeclaringType().getName(), LocationAwareLogger.WARN_INT, formatString, new Object[] { methodName, duration }, null); + } + + if (-1 != methodLog.durationLimit() && duration > methodLog.durationLimit()) { + if (null == methodName) { + methodName = convertMethodName(signature.getName()); + } + String formatString; + if (logger.isEnabledFor(traceLogLevel)) { + formatString = DURATION_THRESHOLD_MSG_W_TRACE; + } else { + formatString = DURATION_THRESHOLD_MSG; + } + logger.log(null, signature.getDeclaringType().getName(), LocationAwareLogger.WARN_INT, formatString, new Object[] { methodLog.durationLimit(), methodName, duration }, null); + } + + if (logger.isEnabledFor(traceLogLevel)) { + logger.log(null, signature.getDeclaringType().getName(), LocationAwareLogger.TRACE_INT, TRACE_EXIT_FORMAT, new Object[] { signature.getDeclaringType().getName(), signature.getName() }, + null); + } + + return object; + } + + /** + * Converts the method name into something more 'readable'.
+ * getMyName: 'Get My Name'
+ * loadAllPersons: 'Load All Persons' + * + * @param name + * The original method name. + * @return The converted readable name string. + */ + private String convertMethodName(String name) { + // split the name string at each uppercase char + String[] r = SPLIT_METHOD_PATTERN.split(name, 0); + StringBuilder builder = new StringBuilder(); + + String first = r[0]; + // first character to upper case + builder.append(Character.toUpperCase(first.charAt(0))); + builder.append(first.substring(1)); + for (int i = 1; i < r.length; i++) { + builder.append(' '); + builder.append(r[i]); + } + + return builder.toString(); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/spring/exporter/KryoHttpInvokerServiceExporter.java b/CMR/src/info/novatec/inspectit/cmr/spring/exporter/KryoHttpInvokerServiceExporter.java new file mode 100644 index 000000000..91c4df053 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/spring/exporter/KryoHttpInvokerServiceExporter.java @@ -0,0 +1,67 @@ +package info.novatec.inspectit.cmr.spring.exporter; + +import info.novatec.inspectit.storage.serializer.ISerializer; +import info.novatec.inspectit.storage.serializer.SerializationException; +import info.novatec.inspectit.storage.serializer.provider.SerializationManagerProvider; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter; +import org.springframework.remoting.support.RemoteInvocation; +import org.springframework.remoting.support.RemoteInvocationResult; + +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +/** + * This service exporter using kryo for (de-)serialization is nearly the same as the one with plain + * java serialization. + * + * @author Patrice Bouillet + * + */ +public class KryoHttpInvokerServiceExporter extends HttpInvokerServiceExporter { + + /** + * The serialization manager. + */ + @Autowired + private SerializationManagerProvider serializationManagerProvider; + + /** + * {@inheritDoc} + */ + @Override + protected RemoteInvocation readRemoteInvocation(HttpServletRequest request, InputStream is) throws IOException, ClassNotFoundException { + try { + ISerializer serializer = serializationManagerProvider.createSerializer(); + return (RemoteInvocation) serializer.deserialize(new Input(is)); + } catch (SerializationException e) { + throw new IOException(e); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeRemoteInvocationResult(HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result, OutputStream os) throws IOException { + try { + if (!result.hasException()) { + Object value = result.getValue(); + result = new RemoteInvocationResult(value); + } + ISerializer serializer = serializationManagerProvider.createSerializer(); + serializer.serialize(result, new Output(os)); + } catch (SerializationException e) { + throw new IOException(e); + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/spring/exporter/KryoNetRmiServiceExporter.java b/CMR/src/info/novatec/inspectit/cmr/spring/exporter/KryoNetRmiServiceExporter.java new file mode 100644 index 000000000..0ec923ecd --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/spring/exporter/KryoNetRmiServiceExporter.java @@ -0,0 +1,92 @@ +package info.novatec.inspectit.cmr.spring.exporter; + +import info.novatec.inspectit.kryonet.Server; +import info.novatec.inspectit.kryonet.rmi.ObjectSpace; +import info.novatec.inspectit.spring.logger.Log; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Exporter that starts the {@link Server}, binds it to the given TCP port and register the object + * to be used remotely. + * + * @author Ivan Senic + * + */ +public class KryoNetRmiServiceExporter { + + /** + * Logger for the class. + */ + @Log + Logger log; + + /** + * Service to export. + */ + private Object service; + + /** + * Service interface. + */ + private String serviceInterface; + + /** + * Id in the object space to export service within. + */ + private int serviceId; + + /** + * Server to register remote object to. + */ + @Autowired + private ObjectSpace objectSpace; + + /** + * Prepares the server and register the service for remote usage. + */ + @PostConstruct + protected void prepare() { + if (null == service) { + throw new BeanInitializationException("The service to export with the kryonet RMI must not be null."); + } + + objectSpace.register(serviceId, service); + log.info("|-Service " + serviceInterface + " exported and available via kryonet RMI with the ID " + serviceId); + } + + /** + * Sets {@link #service}. + * + * @param service + * New value for {@link #service} + */ + public void setService(Object service) { + this.service = service; + } + + /** + * Sets {@link #serviceInterface}. + * + * @param serviceInterface + * New value for {@link #serviceInterface} + */ + public void setServiceInterface(String serviceInterface) { + this.serviceInterface = serviceInterface; + } + + /** + * Sets {@link #serviceId}. + * + * @param serviceId + * New value for {@link #serviceId} + */ + public void setServiceId(int serviceId) { + this.serviceId = serviceId; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/spring/exporter/RemotingExporter.java b/CMR/src/info/novatec/inspectit/cmr/spring/exporter/RemotingExporter.java new file mode 100644 index 000000000..d5adb1e32 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/spring/exporter/RemotingExporter.java @@ -0,0 +1,206 @@ +package info.novatec.inspectit.cmr.spring.exporter; + +import info.novatec.inspectit.cmr.service.ServiceExporterType; +import info.novatec.inspectit.cmr.service.ServiceInterface; + +import java.lang.annotation.Annotation; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +/** + * Provides automatic remote export of services found inside a Spring context. + * + *

+ * When a bean is annotated with {@link Service} and implements an interface annotated with + * {@link ServiceInterface}, the bean will be exported as a service with this interface. This is + * done after the Spring context is refreshed or started so that we are sure that any + * {@link BeanPostProcessor}s and AOP advise is applied. + * + *

+ * This exporter bean is registered in the spring context so that all lifecycles are passed and this + * bean can be used as a reference candidate for other spring beans. The name is either being + * specified statically in the annotation or dynamically via the name of the service bean + + * "Exporter" at the end. + * + *

+ * + * IMPORTANT: The class code is copied/taken/based from Spring JIRA (SPR-3926). Original + * authors are James Douglas and Henno Vermeulen. + * + * @author James Douglas + * @author Henno Vermeulen + * @author Patrice Bouillet + */ +@Component +public class RemotingExporter implements BeanFactoryPostProcessor { + + /** + * The logger for this class. Cannot be injected via Spring. + */ + private static final Logger LOG = LoggerFactory.getLogger(RemotingExporter.class); + + /** + * The annotation defining a class being a service. + */ + private Class serviceAnnotationType = Service.class; + + /** + * The annotation defining an interface being a service interface. + */ + private Class serviceInterfaceAnnotationType = ServiceInterface.class; + + /** + * {@inheritDoc} + * + * We look for the service annotation on the bean class found in the {@link BeanDefinition} + * before any {@link BeanPostProcessor}s or AOP advice is applied. + */ + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { + LOG.info("|-RemoteExporter: Processing Beans for remote export"); + + for (String beanName : beanFactory.getBeanDefinitionNames()) { + BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName); + if (beanDef.isAbstract()) { + if (LOG.isDebugEnabled()) { + LOG.debug("|- RemoteExporter: Skipping abstract Bean '" + beanName + "'"); + } + continue; + } + + if (null == beanDef.getBeanClassName()) { + if (LOG.isDebugEnabled()) { + LOG.debug("|- RemoteExporter: Skipping Bean '" + beanName + "' which has no bean class defined"); + } + continue; + } + + Class serviceInterface = null; + Class serviceClass; + String beanClassName = beanDef.getBeanClassName(); + if (null == beanClassName) { + // bean class name is null for beans defined via @Configuration + continue; + } + try { + serviceClass = Class.forName(beanClassName); + serviceInterface = findServiceInterface(serviceClass); + } catch (ClassNotFoundException e) { + throw new BeanCreationException("class of bean " + beanName + " not found", e); + } + + if (serviceInterface != null) { + if (LOG.isDebugEnabled()) { + LOG.debug("|- RemoteExporter: Found service Bean with name '" + beanName + "' and interface " + serviceInterface); + } + + // creating the bean definition so that the bean is registered in the spring + // container + Annotation annotation = AnnotationUtils.findAnnotation(serviceClass, serviceInterfaceAnnotationType); + RootBeanDefinition definition; + + MutablePropertyValues values = new MutablePropertyValues(); + values.add("service", new RuntimeBeanReference(beanName)); + values.add("serviceInterface", new TypedStringValue(serviceInterface.getCanonicalName())); + + ServiceExporterType type = (ServiceExporterType) AnnotationUtils.getValue(annotation, "exporter"); + switch (type) { + case RMI: + definition = new RootBeanDefinition(KryoNetRmiServiceExporter.class); + if (annotationPropertySet(annotation, "serviceId")) { + int serviceId = (int) AnnotationUtils.getValue(annotation, "serviceId"); + values.add("serviceId", serviceId); + } + break; + case HTTP: + definition = new RootBeanDefinition(KryoHttpInvokerServiceExporter.class); + break; + default: + throw new BeanCreationException("Could not create service exporter bean because exporter type is not handled: " + type); + } + + definition.setPropertyValues(values); + definition.setAutowireCandidate(true); + + BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; + registry.registerBeanDefinition(getNameForExporterBean(beanName, annotation), definition); + + if (LOG.isDebugEnabled()) { + LOG.debug("|- RemoteExporter: Registered new Bean: " + beanName + "Exporter"); + } + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("|- RemoteExporter: Skipping Bean because no remotable interface was found '" + beanName + "'"); + } + } + } + } + + /** + * Checks for if an annotation property is set by overriding the default value. + * + * @param annotation + * the annotation to check for. + * @param attributeName + * the name of the attribute to compare the default and current value. + * @return if the annotation property has been set. + */ + private boolean annotationPropertySet(Annotation annotation, String attributeName) { + Object defaultValue = AnnotationUtils.getDefaultValue(annotation, attributeName); + Object currentValue = AnnotationUtils.getValue(annotation, attributeName); + return !currentValue.equals(defaultValue); + } + + /** + * Creates the name of the exporter bean under which it will be exposed in the spring container. + * + * @param beanName + * the original service bean name. + * @param annotation + * the annotation reference used to extract the name definition - if any. + * @return the name of the exporter bean. + */ + private String getNameForExporterBean(String beanName, Annotation annotation) { + String name = (String) AnnotationUtils.getValue(annotation, "name"); + if (null != name && !"".equals(name.trim())) { + return name; + } else { + return beanName + "Exporter"; + } + } + + /** + * Searches for a service interface on the passed class. + * + * @param serviceClass + * the class to look for a service interface. + * @return returns the service interface. + */ + private Class findServiceInterface(Class serviceClass) { + Class serviceInterface = null; + if (AnnotationUtils.isAnnotationDeclaredLocally(serviceAnnotationType, serviceClass)) { + for (Class interfaceClass : serviceClass.getInterfaces()) { + if (AnnotationUtils.isAnnotationDeclaredLocally(serviceInterfaceAnnotationType, interfaceClass)) { + serviceInterface = interfaceClass; + } + } + } + return serviceInterface; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/storage/CachingWriteDataProcessorProvider.java b/CMR/src/info/novatec/inspectit/cmr/storage/CachingWriteDataProcessorProvider.java new file mode 100644 index 000000000..ec1ff4a71 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/storage/CachingWriteDataProcessorProvider.java @@ -0,0 +1,111 @@ +package info.novatec.inspectit.cmr.storage; + +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.indexing.IIndexQuery; +import info.novatec.inspectit.indexing.aggregation.Aggregators; +import info.novatec.inspectit.indexing.aggregation.IAggregator; +import info.novatec.inspectit.indexing.query.factory.impl.ExceptionSensorDataQueryFactory; +import info.novatec.inspectit.indexing.query.factory.impl.SqlStatementDataQueryFactory; +import info.novatec.inspectit.indexing.query.factory.impl.TimerDataQueryFactory; +import info.novatec.inspectit.indexing.query.provider.impl.StorageIndexQueryProvider; +import info.novatec.inspectit.indexing.storage.impl.StorageIndexQuery; +import info.novatec.inspectit.storage.processor.write.AbstractWriteDataProcessor; +import info.novatec.inspectit.storage.processor.write.impl.QueryCachingDataProcessor; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; + +/** + * Configuration class for specifying the caching processors for the storage writer. + *

+ * These will be autowired to each storage writer. + * + * @author Ivan Senic + * + */ +@Configuration +public class CachingWriteDataProcessorProvider { + + /** + * {@link StorageIndexQueryProvider} is needed here because we are caching storage queries. + */ + @Autowired + private StorageIndexQueryProvider storageIndexQueryProvider; + + /** + * {@link TimerDataQueryFactory}. + */ + private TimerDataQueryFactory timerDataQueryFactory; + + /** + * {@link SqlStatementDataQueryFactory}. + */ + private SqlStatementDataQueryFactory sqlStatementDataQueryFactory; + + /** + * {@link ExceptionSensorDataQueryFactory}. + */ + private ExceptionSensorDataQueryFactory exceptionSensorDataQueryFactory; + + /** + * @return Returns {@link AbstractWriteDataProcessor} for caching the {@link TimerData} view. + */ + @Bean + @Lazy + @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public AbstractWriteDataProcessor getTimerDataCachingDataProcessor() { + IIndexQuery query = timerDataQueryFactory.getAggregatedTimerDataQuery(new TimerData(), null, null); + IAggregator aggregator = Aggregators.TIMER_DATA_AGGREGATOR; + return new QueryCachingDataProcessor<>(query, aggregator); + } + + /** + * @return Returns {@link AbstractWriteDataProcessor} for caching the {@link SqlStatementData} + * view. + */ + @Bean + @Lazy + @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public AbstractWriteDataProcessor getSqlDataCachingDataProcessor() { + IIndexQuery query = sqlStatementDataQueryFactory.getAggregatedSqlStatementsQuery(new SqlStatementData(), null, null); + IAggregator aggregator = Aggregators.SQL_STATEMENT_DATA_AGGREGATOR; + return new QueryCachingDataProcessor<>(query, aggregator); + } + + /** + * @return Returns {@link AbstractWriteDataProcessor} for caching the + * {@link ExceptionSensorData} grouped view. + */ + @Bean + @Lazy + @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public AbstractWriteDataProcessor getGroupedExceptionsDataCachingDataProcessor() { + IIndexQuery query = exceptionSensorDataQueryFactory.getDataForGroupedExceptionOverviewQuery(new ExceptionSensorData(), null, null); + IAggregator aggregator = Aggregators.GROUP_EXCEPTION_OVERVIEW_AGGREGATOR; + return new QueryCachingDataProcessor<>(query, aggregator); + } + + /** + * Init. + */ + @PostConstruct + public void initFactories() { + timerDataQueryFactory = new TimerDataQueryFactory<>(); + timerDataQueryFactory.setIndexQueryProvider(storageIndexQueryProvider); + + sqlStatementDataQueryFactory = new SqlStatementDataQueryFactory<>(); + sqlStatementDataQueryFactory.setIndexQueryProvider(storageIndexQueryProvider); + + exceptionSensorDataQueryFactory = new ExceptionSensorDataQueryFactory<>(); + exceptionSensorDataQueryFactory.setIndexQueryProvider(storageIndexQueryProvider); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/storage/CmrStorageManager.java b/CMR/src/info/novatec/inspectit/cmr/storage/CmrStorageManager.java new file mode 100644 index 000000000..dbcfc786c --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/storage/CmrStorageManager.java @@ -0,0 +1,1221 @@ +package info.novatec.inspectit.cmr.storage; + +import info.novatec.inspectit.cmr.cache.IBuffer; +import info.novatec.inspectit.cmr.dao.StorageDataDao; +import info.novatec.inspectit.cmr.dao.impl.DefaultDataDaoImpl; +import info.novatec.inspectit.cmr.service.IServerStatusService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.cmr.WritingStatus; +import info.novatec.inspectit.spring.logger.Log; +import info.novatec.inspectit.storage.IStorageData; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageData.StorageState; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.StorageFileType; +import info.novatec.inspectit.storage.StorageManager; +import info.novatec.inspectit.storage.StorageWriter; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.processor.AbstractDataProcessor; +import info.novatec.inspectit.storage.processor.impl.TimeFrameDataProcessor; +import info.novatec.inspectit.storage.recording.RecordingProperties; +import info.novatec.inspectit.storage.recording.RecordingState; +import info.novatec.inspectit.storage.serializer.ISerializer; +import info.novatec.inspectit.storage.serializer.SerializationException; +import info.novatec.inspectit.storage.util.CopyMoveFileVisitor; +import info.novatec.inspectit.storage.util.DeleteFileVisitor; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import javax.annotation.PostConstruct; + +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.mutable.MutableLong; +import org.apache.commons.lang.mutable.MutableObject; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import com.esotericsoftware.kryo.io.Input; + +/** + * Storage manager for the CMR. Manages creation, opening and closing of storages, as well as + * recording. + * + * @author Ivan Senic + * + */ +@Component +public class CmrStorageManager extends StorageManager implements ApplicationListener { // NOPMD + + /** + * The log of this class. + */ + @Log + Logger log; + + /** + * The fixed rate of the refresh rate for gathering the statistics. + */ + private static final int UPDATE_RATE = 30000; + + /** + * {@link DefaultDataDaoImpl}. + */ + @Autowired + StorageDataDao storageDataDao; + + /** + * Buffer for dealing with copy from buffer action. + */ + @Autowired + IBuffer buffer; + + /** + * {@link StorageData} for currently active recorder. + */ + private volatile StorageData recorderStorageData = null; + + /** + * {@link StorageWriter} provider. + */ + @Autowired + CmrStorageWriterProvider storageWriterProvider; + + /** + * Opened storages and their writers. + */ + private Map openedStoragesMap = new ConcurrentHashMap(8, 0.75f, 1); + + /** + * Existing storages. + */ + private Set existingStoragesSet; + + /** + * {@link StorageRecorder} to deal with recording. + */ + @Autowired + CmrStorageRecorder storageRecorder; + + /** + * {@link IServerStatusService}. + */ + @Autowired + IServerStatusService serverStatusService; + + /** + * Current cmr version. + */ + private String cmrVersion; + + /** + * Creates new storage. + * + * @param storageData + * Storage. + * @throws IOException + * if {@link IOException} occurs. + * @throws SerializationException + * If serialization fails. + */ + public void createStorage(StorageData storageData) throws IOException, SerializationException { + storageData.setId(getRandomUUIDString()); + storageData.setCmrVersion(cmrVersion); + writeStorageDataToDisk(storageData); + existingStoragesSet.add(storageData); + } + + /** + * Opens existing storage if it is not already opened. + * + * @param storageData + * Storage to open. + * @return {@link StorageWriter} created for this storage. Of null if no new writer + * is created. + * @throws IOException + * If {@link IOException} occurs. + * @throws SerializationException + * If exception occurs during update of storage data. + * @throws StorageException + * If provided storage data does not exist or if the storage is closed. + */ + public StorageWriter openStorage(StorageData storageData) throws IOException, SerializationException, StorageException { + StorageData local = getLocalStorageDataObject(storageData); + synchronized (local) { + if (isStorageClosed(local)) { + throw new StorageException("Already closed storage " + local + " can not be opened."); + } + if (!isStorageOpen(local)) { + local.markOpened(); + StorageWriter writer = storageWriterProvider.getCmrStorageWriter(); + openedStoragesMap.put(local, writer); + writer.prepareForWrite(local); + writeStorageDataToDisk(local); + return writer; + } + } + return null; + } + + /** + * Closes the storage if it is open. + * + * @param storageData + * Storage. + * @throws StorageException + * When storage that should be closed is used for recording or it is already closed. + * @throws SerializationException + * If serialization fails. + * @throws IOException + * If {@link IOException} occurs. + */ + public void closeStorage(StorageData storageData) throws StorageException, IOException, SerializationException { + StorageData local = getLocalStorageDataObject(storageData); + synchronized (local) { + if ((storageRecorder.isRecordingOn() || storageRecorder.isRecordingScheduled()) && Objects.equals(local, recorderStorageData)) { + throw new StorageException("Storage " + local + " can not be finalized because it is currently used for recording purposes."); + } else if (isStorageClosed(local)) { + throw new StorageException("Already closed storage " + local + " can not be closed again."); + } + + StorageWriter writer = openedStoragesMap.get(local); + if (writer != null) { + writer.closeStorageWriter(); + } + openedStoragesMap.remove(local); + local.setDiskSize(getDiskSizeForStorage(local)); + local.markClosed(); + writeStorageDataToDisk(local); + } + } + + /** + * Deletes a storage information and files from disk. + * + * @param storageData + * {@link StorageData} to delete. + * @throws StorageException + * If storage is not closed. + * @throws IOException + * If {@link IOException} occurs. + */ + public void deleteStorage(StorageData storageData) throws StorageException, IOException { + StorageData local = getLocalStorageDataObject(storageData); + synchronized (local) { + if ((storageRecorder.isRecordingOn() || storageRecorder.isRecordingScheduled()) && Objects.equals(local, recorderStorageData)) { + throw new StorageException("Storage " + local + " can not be finalized because it is currently used for recording purposes."); + } + if (local.isStorageOpened()) { + StorageWriter writer = openedStoragesMap.get(local); + if (writer != null) { + writer.cancel(); + } + openedStoragesMap.remove(local); + } + deleteCompleteStorageDataFromDisk(local); + existingStoragesSet.remove(local); + } + } + + /** + * If the recording is active, returns the storage that is used for storing recording data. + * + * @return Storage that is used for recording, or null if recording is not active. + */ + public StorageData getRecordingStorage() { + return recorderStorageData; + } + + /** + * Returns the properties used for the current recording on the CMR. + * + * @return {@link RecordingProperties} that are used for recording, or null if recording is not + * active. + */ + public RecordingProperties getRecordingProperties() { + return storageRecorder.getRecordingProperties(); + } + + /** + * Returns the recording state. + * + * @return Returns the recording state. + * @See {@link RecordingState} + */ + public RecordingState getRecordingState() { + return storageRecorder.getRecordingState(); + } + + /** + * Returns the {@link WritingStatus} for the storage that is currently used as a recording one + * or null if the recording is not active. + * + * @return {@link WritingStatus} if recording is active. Null otherwise. + */ + public WritingStatus getRecordingStatus() { + StorageWriter recordingStorageWriter = storageRecorder.getStorageWriter(); + if (null != recordingStorageWriter) { + return recordingStorageWriter.getWritingStatus(); + } else { + return null; + } + } + + /** + * Starts recording on the provided storage if recording is not active. If storage is not + * created it will be. If it is not open, it will be. + * + * @param storageData + * Storage. + * @param recordingProperties + * Recording properties. Must not be null. + * @throws IOException + * If {@link IOException} occurs while creating and opening the storage. + * @throws SerializationException + * If serialization fails when creating the storage. + * @throws StorageException + * If recording can not be started for some reason. + */ + public void startOrScheduleRecording(StorageData storageData, RecordingProperties recordingProperties) throws IOException, SerializationException, StorageException { + if (!isStorageExisting(storageData)) { + this.createStorage(storageData); + } + StorageData local = getLocalStorageDataObject(storageData); + if (!isStorageOpen(local)) { + this.openStorage(local); + } + synchronized (this) { + if (!storageRecorder.isRecordingOn() && !storageRecorder.isRecordingScheduled()) { + StorageWriter storageWriter = openedStoragesMap.remove(local); + storageRecorder.startOrScheduleRecording(storageWriter, recordingProperties); + recorderStorageData = local; + recorderStorageData.markRecording(); + writeStorageDataToDisk(recorderStorageData); + } + } + } + + /** + * Stops recording. + * + * @throws SerializationException + * If serialization fails during write {@link StorageData} to disk. + * @throws IOException + * If IOException occurs during write {@link StorageData} to disk. + * @throws StorageException + * If {@link StorageException} is throw during auto-finalize process. + */ + public void stopRecording() throws IOException, SerializationException, StorageException { + synchronized (this) { + if (storageRecorder.isRecordingOn() || storageRecorder.isRecordingScheduled()) { + boolean autoFinalize = storageRecorder.getRecordingProperties().isAutoFinalize(); + StorageWriter storageWriter = storageRecorder.getStorageWriter(); + storageRecorder.stopRecording(); + recorderStorageData.markOpened(); + openedStoragesMap.put(recorderStorageData, storageWriter); + if (autoFinalize) { + this.closeStorage(recorderStorageData); + } + writeStorageDataToDisk(recorderStorageData); + recorderStorageData = null; // NOPMD + } + } + } + + /** + * Writes one data to the recording storage. + * + * @param dataToRecord + * Data to write. + */ + public void record(DefaultData dataToRecord) { + if (storageRecorder.isRecordingOn() && canWriteMore()) { + storageRecorder.record(dataToRecord); + } else if (storageRecorder.isRecordingOn() && !canWriteMore()) { + try { + stopRecording(); + } catch (Exception e) { + log.warn("Exception occurred trying to automatically stop the recording due to the hard disk space limitation warning.", e); + } + } + } + + /** + * Writes collection of {@link DefaultData} objects to the storage. + * + * @param storageData + * Storage to write. + * @param dataToWrite + * Data to write. + * @param dataProcessors + * Processors that will be used for data writing. Can be null. In this case, the + * direct write is done. + * @param synchronously + * If write will be done synchronously or not. + * @throws StorageException + * If storage is used as a recording storage. + * @throws SerializationException + * If serialization fails during auto-finalization. + * @throws IOException + * If {@link IOException} occurs during auto-finalization. + */ + public void writeToStorage(StorageData storageData, Collection dataToWrite, Collection dataProcessors, boolean synchronously) + throws StorageException, IOException, SerializationException { + StorageData local = getLocalStorageDataObject(storageData); + StorageWriter writer = openedStoragesMap.get(local); + if (writer != null) { + if (synchronously) { + writer.processSynchronously(dataToWrite, dataProcessors); + } else { + writer.process(dataToWrite, dataProcessors); + } + } else if (Objects.equals(local, recorderStorageData)) { + throw new StorageException("Can not write to storage that is currently used as a recording storage."); + } else if (local.getState() == StorageState.CLOSED) { + throw new StorageException("Can not write to closed storage"); + } else { + log.error("Writer for the not closed storage " + local + " not available."); + throw new StorageException("Writer for the not closed storage " + local + " not available."); + } + } + + /** + * Copies the content of the current CMR buffer to the Storage. + * + * @param storageData + * Storage to copy data to. + * @param platformIdents + * List of agent IDs. + * @param dataProcessors + * Processors that will be used for data writing. + * @param autoFinalize + * If the storage where action is performed should be auto-finalized after the write. + * @throws StorageException + * If storage is used as a recording storage. + * @throws SerializationException + * If storage needs to be created, and serialization fails. + * @throws IOException + * If IO exception occurs. + */ + public void copyBufferToStorage(StorageData storageData, List platformIdents, Collection dataProcessors, boolean autoFinalize) throws StorageException, IOException, + SerializationException { + if (!isStorageExisting(storageData)) { + this.createStorage(storageData); + } + StorageData local = getLocalStorageDataObject(storageData); + if (!isStorageOpen(local)) { + this.openStorage(local); + } + + DefaultData oldestBufferElement = buffer.getOldestElement(); + // only copy if we have smth in the buffer + if (null != oldestBufferElement) { + + // oldest date is the buffer oldest date, we don't include data from before + Date fromDate = new Date(oldestBufferElement.getTimeStamp().getTime()); + Date toDate = null; + + // check if we have the time-frame limit + for (AbstractDataProcessor dataProcessor : dataProcessors) { + if (dataProcessor instanceof TimeFrameDataProcessor) { + TimeFrameDataProcessor timeFrameDataProcessor = (TimeFrameDataProcessor) dataProcessor; + // update dates + if (timeFrameDataProcessor.getFromDate().after(fromDate)) { + fromDate = timeFrameDataProcessor.getFromDate(); + } + toDate = timeFrameDataProcessor.getToDate(); + break; + } + } + + for (Long platformId : platformIdents) { + List toWriteList = storageDataDao.getAllDefaultDataForAgent(platformId.longValue(), fromDate, toDate); + this.writeToStorage(local, toWriteList, dataProcessors, true); + } + } + + if (autoFinalize) { + this.closeStorage(local); + } + updateExistingStorageSize(local); + } + + /** + * Copies set of template data to storage. The storage does not have to be opened before action + * can be executed (storage will be created/opened first in this case) + * + * @param storageData + * {@link StorageData} to copy to. + * @param elementIds + * IDs of the elements to be saved. + * @param platformIdent + * Platform ident elements belong to. + * @param dataProcessors + * Processors to process the data. Can be null, then the data is only copied with no + * processing. + * @param autoFinalize + * If the storage where action is performed should be auto-finalized after the write. + * @throws IOException + * If {@link IOException} occurs. + * @throws SerializationException + * If serialization fails when storage needs to be created/opened. + * @throws StorageException + * If {@link StorageException} occurs. + */ + public void copyDataToStorage(StorageData storageData, Collection elementIds, long platformIdent, Collection dataProcessors, boolean autoFinalize) throws IOException, + SerializationException, StorageException { + if (!isStorageExisting(storageData)) { + this.createStorage(storageData); + } + StorageData local = getLocalStorageDataObject(storageData); + if (!isStorageOpen(local)) { + this.openStorage(local); + } + + List toWriteList = storageDataDao.getDataFromIdList(elementIds, platformIdent); + this.writeToStorage(local, toWriteList, dataProcessors, true); + if (autoFinalize) { + this.closeStorage(local); + } + updateExistingStorageSize(local); + } + + /** + * Closes all opened storages. This method should only be called when the CMR shutdown hook is + * activated to ensure that no data is lost. + * + * @throws SerializationException + * @throws IOException + */ + protected void closeAllStorages() { + if (storageRecorder.isRecordingOn() || storageRecorder.isRecordingScheduled()) { + try { + stopRecording(); + } catch (Exception e) { + log.warn("Recording storage could not be finalized during the CMR shut-down.", e); + + } + } + for (StorageData openedStorage : openedStoragesMap.keySet()) { + try { + this.closeStorage(openedStorage); + } catch (Exception e) { + log.warn("Storage " + openedStorage + " could not be finalized during the CMR shut-down.", e); + } + } + } + + /** + * Returns the storage data based on the ID. This method can be helpful when the updated version + * of {@link StorageData} needs to be retrieved. + * + * @param id + * ID of storage. + * @return {@link StorageData} + */ + public StorageData getStorageData(String id) { + for (StorageData storageData : existingStoragesSet) { + if (storageData.getId().equals(id)) { + return storageData; + } + } + return null; + } + + /** + * Returns list of existing storages. + * + * @return Returns list of existing storages. + */ + public List getExistingStorages() { + List list = new ArrayList(); + list.addAll(existingStoragesSet); + return list; + } + + /** + * Returns list of opened storages. + * + * @return Returns list of opened storages. + */ + public List getOpenedStorages() { + List list = new ArrayList(); + list.addAll(openedStoragesMap.keySet()); + return list; + } + + /** + * Returns list of readable storages. + * + * @return Returns list of readable storages. + */ + public List getReadableStorages() { + List list = new ArrayList(); + for (StorageData storageData : existingStoragesSet) { + if (storageData.isStorageClosed()) { + list.add(storageData); + } + } + return list; + } + + /** + * Returns if the storage is opened, and thus if the write to the storage can be executed. + * + * @param storageData + * Storage to check. + * @return True if storage is opened, otherwise false. + */ + public boolean isStorageOpen(StorageData storageData) { + for (StorageData existing : openedStoragesMap.keySet()) { + if (existing.getId().equals(storageData.getId())) { + return true; + } + } + return false; + } + + /** + * Returns if the storage is existing. + * + * @param storageData + * Storage to check. + * @return True if storage exists, otherwise false. + */ + public boolean isStorageExisting(StorageData storageData) { + for (StorageData existing : existingStoragesSet) { + if (existing.getId().equals(storageData.getId())) { + return true; + } + } + return false; + } + + /** + * Returns if the storage is closed. + * + * @param storageData + * Storage to check. + * @return True if storage is closed, in any other situation false. + */ + public boolean isStorageClosed(StorageData storageData) { + for (StorageData existing : existingStoragesSet) { + if (existing.getId().equals(storageData.getId())) { + return existing.isStorageClosed(); + } + } + return false; + } + + /** + * Returns the amount of writing tasks storage still has to process. Note that this is an + * approximate number. + * + * @param storageData + * Storage data to get information for. + * @return Returns number of queued tasks. Note that if the storage is not in writable mode + * 0 will be returned. + */ + public long getStorageQueuedWriteTaskCount(StorageData storageData) { + try { + StorageData local = getLocalStorageDataObject(storageData); + if (!isStorageOpen(local)) { + return 0; + } + + StorageWriter storageWriter = openedStoragesMap.get(local); + if (null == storageWriter) { + return 0; + } else { + return storageWriter.getQueuedTaskCount(); + } + } catch (StorageException e) { + return 0; + } + } + + /** + * {@inheritDoc} + */ + public Path getStoragePath(IStorageData storageData) { + return getDefaultStorageDirPath().resolve(storageData.getStorageFolder()); + } + + /** + * {@inheritDoc} + */ + @Override + protected Path getDefaultStorageDirPath() { + return Paths.get(getStorageDefaultFolder()).toAbsolutePath(); + } + + /** + * Returns list of files paths with given extension for a storage in HTTP form. + * + * @param storageData + * Storage. + * @param extension + * Files extension. + * @return Returns the map containing pair with file name with given extension for a storage in + * HTTP form and size of each file. + * @throws IOException + * If {@link IOException} occurs. + */ + public Map getFilesHttpLocation(StorageData storageData, final String extension) throws IOException { + Path storagePath = getStoragePath(storageData); + if (storagePath == null || !Files.isDirectory(storagePath)) { + return Collections.emptyMap(); + } + + final List filesPaths = new ArrayList(); + Files.walkFileTree(storagePath, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.toString().endsWith(extension)) { + filesPaths.add(file); + } + return super.visitFile(file, attrs); + } + }); + + Map result = new HashMap(); + for (Path path : filesPaths) { + result.put(getPathAsHttp(path), Files.size(path)); + } + + return result; + } + + /** + * Add a label to the storage and saves new state of the storage to the disk. + * + * @param storageData + * {@link StorageData}. + * @param storageLabel + * Label to add. + * @param doOverwrite + * Overwrite if label type already exists and is only one per storage allowed. + * @throws IOException + * If {@link IOException} happens. + * @throws SerializationException + * If {@link SerializationException} happens. + * @throws StorageException + * If provided storage data does not exist. + */ + public void addLabelToStorage(StorageData storageData, AbstractStorageLabel storageLabel, boolean doOverwrite) throws IOException, SerializationException, StorageException { + StorageData local = getLocalStorageDataObject(storageData); + if (null != local) { + local.addLabel(storageLabel, doOverwrite); + writeStorageDataToDisk(local); + } + } + + /** + * Removes label from storage and saves new state of the storage data to the disk. + * + * @param storageData + * {@link StorageData}. + * @param storageLabel + * Label to remove. + * @return True if the label was removed, false otherwise. + * @throws IOException + * If {@link IOException} happens. + * @throws SerializationException + * If {@link SerializationException} happens. + * @throws StorageException + * If provided storage data does not exist. + */ + public boolean removeLabelFromStorage(StorageData storageData, AbstractStorageLabel storageLabel) throws IOException, SerializationException, StorageException { + StorageData local = getLocalStorageDataObject(storageData); + if (null != local) { + boolean removed = local.removeLabel(storageLabel); + writeStorageDataToDisk(local); + return removed; + } + return false; + } + + /** + * Updates the storage data for already existing storage. + * + * @param storageData + * Storage data containing update values. + * @throws StorageException + * If storage does not exists. + * @throws SerializationException + * If serialization fails. + * @throws IOException + * If IO operation fails. + */ + public void updateStorageData(StorageData storageData) throws StorageException, IOException, SerializationException { + StorageData local = getLocalStorageDataObject(storageData); + if (null == local) { + throw new StorageException("Storage to update does not exists."); + } else { + synchronized (local) { + local.setName(storageData.getName()); + local.setDescription(storageData.getDescription()); + writeStorageDataToDisk(local); + } + } + } + + /** + * Returns the status of the active storage writers. This can be used for logging purposes. + * + * @return Returns the status of the active storage writers. + */ + public Map getWritersStatus() { + Map map = new HashMap(); + for (Map.Entry entry : openedStoragesMap.entrySet()) { + map.put(entry.getKey(), entry.getValue().getExecutorServiceStatus()); + } + if (storageRecorder.isRecordingOn()) { + StorageData storageData = recorderStorageData; + StorageWriter storageWriter = storageRecorder.getStorageWriter(); + if (null != storageData && null != storageWriter) { + map.put(storageData, storageWriter.getExecutorServiceStatus()); + } + } + return map; + } + + /** + * Updates the size of each existing storage, if it changed. + *

+ * This method is called from a Spring configured job. + * + * @throws IOException + * If {@link IOException} happened during operation. + * @throws SerializationException + * If serialization failed. + */ + @Scheduled(fixedRate = UPDATE_RATE) + protected void updateExistingStoragesSize() throws IOException, SerializationException { + for (StorageData storageData : existingStoragesSet) { + updateExistingStorageSize(storageData); + } + } + + /** + * Updates size of the given storage and saves information to this. + * + * @param storageData + * Storage data. + * @throws IOException + * If {@link IOException} happened during operation. + * @throws SerializationException + * If serialization failed. + */ + private void updateExistingStorageSize(StorageData storageData) throws IOException, SerializationException { + if (null != storageData) { + synchronized (storageData) { + long newSize = getDiskSizeForStorage(storageData); + if (newSize != storageData.getDiskSize()) { + storageData.setDiskSize(newSize); + writeStorageDataToDisk(storageData); + } + } + } + } + + /** + * Checks for the uploaded files in the storage uploads folder and tries to extract data to the + * default storage folder. + * + * @param packedStorageData + * Storage data that is packed in the file that needs to be unpacked. + * + * @throws IOException + * IF {@link IOException} occurs during the file tree walk. + * @throws StorageException + * If there is not enough space for the unpacking the storage. + */ + public void unpackUploadedStorage(final IStorageData packedStorageData) throws IOException, StorageException { + long storageBytesLeft = getBytesHardDriveOccupancyLeft(); + if (packedStorageData.getDiskSize() > storageBytesLeft) { + throw new StorageException("Uploaded storage " + packedStorageData + " can not be unpacked because there is not enough disk space left for storage data on the CMR."); + } + + Path uploadPath = Paths.get(this.getStorageUploadsFolder()); + if (Files.notExists(uploadPath)) { + throw new IOException("Can not perform storage unpacking. The main upload path " + uploadPath.toString() + " does not exist."); + } else { + Files.walkFileTree(uploadPath, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + try { + // skip all other files + if (!file.toString().endsWith(StorageFileType.ZIP_STORAGE_FILE.getExtension())) { + return FileVisitResult.CONTINUE; + } + + IStorageData storageData = getStorageDataFromZip(file); + if (!Objects.equals(packedStorageData, storageData)) { + // go to next file if the file that we found does not hold the correct + // storage to unpack + return FileVisitResult.CONTINUE; + } + + if (null != storageData) { + StorageData importedStorageData = new StorageData(storageData); + if (existingStoragesSet.add(importedStorageData)) { + printStorageCmrVersionWarn(storageData); + + unzipStorageData(file, getStoragePath(importedStorageData)); + Path localInformation = getStoragePath(importedStorageData).resolve(importedStorageData.getId() + StorageFileType.LOCAL_STORAGE_FILE.getExtension()); + Files.deleteIfExists(localInformation); + writeStorageDataToDisk(importedStorageData); + } else { + log.info("Uploaded storage file " + file.toString() + " contains the storage that is already available on the CMR. File will be deleted."); + } + } + Files.deleteIfExists(file); + } catch (Exception e) { + log.warn("Uploaded storage file " + file.toString() + " is not of correct type and can not be extracted. File will be deleted.", e); + } + return FileVisitResult.CONTINUE; + } + }); + } + } + + /** + * Creates a storage form the uploaded local storage directory. + * + * @param localStorageData + * Local storage information. + * @throws IOException + * If {@link IOException} occurs. + * @throws StorageException + * If there is not enough space for the unpacking the storage. + * @throws SerializationException + * If serialization fails. + */ + public void createStorageFromUploadedDir(final IStorageData localStorageData) throws IOException, StorageException, SerializationException { + long storageBytesLeft = getBytesHardDriveOccupancyLeft(); + if (localStorageData.getDiskSize() > storageBytesLeft) { + throw new StorageException("Uploaded storage " + localStorageData + " can not be unpacked because there is not enough disk space left for storage data on the CMR."); + } + + Path uploadPath = Paths.get(this.getStorageUploadsFolder()); + if (Files.notExists(uploadPath)) { + throw new IOException("Can not perform storage unpacking. The main upload path " + uploadPath.toString() + " does not exist."); + } else { + final MutableObject storageUploadPath = new MutableObject(); + final MutableObject uploadedStorageData = new MutableObject(); + final ISerializer serializer = getSerializationManagerProvider().createSerializer(); + Files.walkFileTree(uploadPath, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + // skip all other files, search for the local data + if (!file.toString().endsWith(localStorageData.getId() + StorageFileType.LOCAL_STORAGE_FILE.getExtension())) { + return FileVisitResult.CONTINUE; + } + + // when found confirm it is the one we wanted to upload + InputStream inputStream = null; + Input input = null; + try { + inputStream = Files.newInputStream(file, StandardOpenOption.READ); + input = new Input(inputStream); + Object deserialized = serializer.deserialize(input); + if (Objects.equals(deserialized, localStorageData)) { + uploadedStorageData.setValue(new StorageData(localStorageData)); + storageUploadPath.setValue(file.toAbsolutePath().getParent()); + return FileVisitResult.TERMINATE; + } + } catch (SerializationException e) { + log.warn("Error de-serializing local storage file.", e); + } finally { + if (null != input) { + input.close(); + } + } + return FileVisitResult.CONTINUE; + } + }); + + // do the rest out of the file walk + Path parentDir = (Path) storageUploadPath.getValue(); + StorageData storageData = (StorageData) uploadedStorageData.getValue(); + if (null != storageData && null != parentDir) { + Path storageDir = getStoragePath(storageData); + if (existingStoragesSet.add(storageData)) { + if (Files.notExists(storageDir)) { + printStorageCmrVersionWarn(storageData); + + Files.walkFileTree(parentDir, new CopyMoveFileVisitor(parentDir, storageDir, true)); + Path localInformation = getStoragePath(storageData).resolve(storageData.getId() + StorageFileType.LOCAL_STORAGE_FILE.getExtension()); + Files.deleteIfExists(localInformation); + writeStorageDataToDisk(storageData); + } else { + throw new IOException("Directory to place uploaded storage already exists."); + } + } else { + log.info("Uploaded storage on path " + parentDir.toString() + " contains the storage that is already available on the CMR. Dir will be deleted."); + Files.walkFileTree(parentDir, new DeleteFileVisitor()); + } + } + } + } + + /** + * Returns location of the file where the cached data for given storage and hash is cached. + * Returns null if no data is cached for given storage and hash. + *

+ * The path is in form "/directory/file.extension". The path can be used in combination to CMR's + * ip and port to get the files via HTTP. + *

+ * For example, if the CMR has the ip localhost and port 8080, the address for the file would + * be: http://localhost:8080/directory/file.extension + * + * @param storageData + * Storage + * @param hash + * Hash that was used for caching. + * @return Returns location of the file where the cached data for given storage and hash is + * cached. Returns null if no data is cached for given storage and hash. + */ + public String getCachedStorageDataFileLocation(StorageData storageData, int hash) { + Path path = super.getCachedDataPath(storageData, hash); + if (Files.exists(path)) { + return getPathAsHttp(path); + } else { + return null; + } + } + + /** + * Returns path in the storage folder that can be used in HTTP requests. + *

+ * Note that for Jetty, root folder to deliver files is /storage/ thus path must be relative + * from it. + * + * @param path + * Path to convert. + * @return String that attached to server ip and port dentes HTTP location of file. + */ + private String getPathAsHttp(Path path) { + StringBuilder stringBuilder = new StringBuilder(); + for (Iterator it = getDefaultStorageDirPath().relativize(path).iterator(); it.hasNext();) { + Path pathPart = it.next(); + stringBuilder.append('/'); + stringBuilder.append(pathPart.toString()); + } + return stringBuilder.toString(); + } + + /** + * Returns the size of the storage on disk. + * + * @param storageData + * Storage. + * @return Size of storage on disk, or 0 if {@link IOException} occurs during calculations. + */ + private long getDiskSizeForStorage(StorageData storageData) { + Path storageDir = getStoragePath(storageData); + try { + final MutableLong size = new MutableLong(0); + Files.walkFileTree(storageDir, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + size.add(attrs.size()); + return super.visitFile(file, attrs); + } + }); + return size.longValue(); + } catch (IOException e) { + return 0; + } + } + + /** + * Returns the local cached object that represent the {@link StorageData}. + * + * @param storageData + * Template. + * @return Local object. + * @throws StorageException + * If local object can not be found. + */ + private StorageData getLocalStorageDataObject(StorageData storageData) throws StorageException { + for (StorageData existing : existingStoragesSet) { + if (existing.getId().equals(storageData.getId())) { + return existing; + } + } + throw new StorageException("Local storage object can not be found with given storage data: " + storageData); + } + + /** + * Loads all existing storages by walking through the default storage directory. + */ + private void loadAllExistingStorages() { + existingStoragesSet = Collections.newSetFromMap(new ConcurrentHashMap()); + + Path defaultDirectory = Paths.get(getStorageDefaultFolder()); + if (!Files.isDirectory(defaultDirectory)) { + return; + } + + final ISerializer serializer = getSerializationManagerProvider().createSerializer(); + try { + Files.walkFileTree(defaultDirectory, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.toString().endsWith(StorageFileType.STORAGE_FILE.getExtension())) { + + InputStream inputStream = null; + Input input = null; + try { + inputStream = Files.newInputStream(file, StandardOpenOption.READ); + input = new Input(inputStream); + Object deserialized = serializer.deserialize(input); + if (deserialized instanceof StorageData) { + StorageData storageData = (StorageData) deserialized; + // do not add any corrupted storages + if (storageData.getState() == StorageState.CLOSED) { + printStorageCmrVersionWarn(storageData); + existingStoragesSet.add(storageData); + } + } + } catch (IOException e) { + log.error("Error reading existing storage data file. File path: " + file.toString() + ".", e); + } catch (SerializationException e) { + log.error("Error deserializing existing storage binary data in file:" + file.toString() + ".", e); + } finally { + if (null != input) { + input.close(); + } + } + } + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + log.error("Error exploring default storage directory. Directory path: " + defaultDirectory.toString() + ".", e); + } + } + + /** + * Clears the upload folder. + */ + private void clearUploadFolder() { + final Path uploadPath = Paths.get(this.getStorageUploadsFolder()); + if (Files.notExists(uploadPath)) { + return; + } + + try { + Files.walkFileTree(uploadPath, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + if (dir.equals(uploadPath)) { + return FileVisitResult.CONTINUE; + } + + if (null == exc) { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } else { + throw exc; + } + } + + }); + } catch (IOException e) { + log.warn("Could not delete the storage upload folder on the start-up.", e); + } + } + + /** + * Returns the unique String that will be used as a StorageData ID. This ID needs to be unique + * not only for the current CMR, but we need to ensure that is unique for all CMRs, because the + * correlation between storage and CMR will be done by this ID. + * + * @return Returns unique string based on the {@link UUID}. + */ + private String getRandomUUIDString() { + return UUID.randomUUID().toString(); + } + + /** + * Prints the warnings if the CMR version saved in the storage does not exists or is different + * from the current CMR version. + * + * @param storageData + * {@link StorageData}. + */ + private void printStorageCmrVersionWarn(IStorageData storageData) { + // inform if the version of the CMR differs or is not available + if (null == storageData.getCmrVersion()) { + log.warn("The storage " + storageData + " does not define the CMR version. The storage might be unstable on the CMR version " + cmrVersion + "."); + } else if (!Objects.equals(storageData.getCmrVersion(), cmrVersion)) { + log.warn("The storage " + storageData + " has different CMR version (" + storageData.getCmrVersion() + ") than the current CMR version(" + cmrVersion + "). The storage might be unstable."); + } + } + + /** + * {@inheritDoc} + */ + @PostConstruct + public void postConstruct() throws Exception { + cmrVersion = serverStatusService.getVersion(); + loadAllExistingStorages(); + updatedStorageSpaceLeft(); + clearUploadFolder(); + } + + /** + * {@inheritDoc} + *

+ * Close all storage on context closing. + */ + @Override + public void onApplicationEvent(ContextClosedEvent event) { + closeAllStorages(); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + ToStringBuilder toStringBuilder = new ToStringBuilder(this); + toStringBuilder.append("existingStoragesSet", existingStoragesSet); + toStringBuilder.append("openedStoragesMap", openedStoragesMap); + toStringBuilder.append("storageRecorder", storageRecorder); + return toStringBuilder.toString(); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/storage/CmrStorageRecorder.java b/CMR/src/info/novatec/inspectit/cmr/storage/CmrStorageRecorder.java new file mode 100644 index 000000000..3223c475b --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/storage/CmrStorageRecorder.java @@ -0,0 +1,313 @@ +package info.novatec.inspectit.cmr.storage; + +import info.novatec.inspectit.cmr.dao.StorageDataDao; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.SystemInformationData; +import info.novatec.inspectit.spring.logger.Log; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.StorageWriter; +import info.novatec.inspectit.storage.processor.AbstractDataProcessor; +import info.novatec.inspectit.storage.recording.RecordingProperties; +import info.novatec.inspectit.storage.recording.RecordingState; + +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Resource; + +import org.apache.commons.collections.CollectionUtils; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * CMR storage recorder that uses the {@link StorageWriter} to provide recording functionality.. + * Handles the scheduling, starting and stopping of the recording. + * + * @author Ivan Senic + * + */ +@Component +public class CmrStorageRecorder { + + /** + * The log of this class. + */ + @Log + Logger log; + + /** + * CMR storage manager. + */ + @Autowired + CmrStorageManager cmrStorageManager; + + /** + * {@link StorageDataDao}. + */ + @Autowired + StorageDataDao storageDataDao; + + /** + * {@link ExecutorService} for tasks of the tree handling. + */ + @Autowired + @Resource(name = "scheduledExecutorService") + ScheduledExecutorService executorService; + + /** + * Future for the task of recording stop. + */ + private ScheduledFuture stopRecordingFuture; + + /** + * Future for the task of recording start. + */ + private ScheduledFuture startRecordingFuture; + + /** + * Set of involved Agents, used after recording to store proper Agent information. + */ + private Set involvedAgentsSet = new HashSet(); + + /** + * Storage writer to use for writing. + */ + private StorageWriter storageWriter; + + /** + * Properties used when recording. + */ + private RecordingProperties recordingProperties; + + /** + * Recording state. By default is not active. + */ + private volatile RecordingState recordingState = RecordingState.OFF; + + /** + * Records this object, by processing it against the all the recording + * {@link AbstractDataProcessor}s that are defined in the {@link RecordingProperties} for this + * {@link StorageWriter}. Processor define which data will be stored, when and in which format. + *

+ * If the processors are not set, then the normal write will be executed. + * + * @param defaultData + * Object to be processed. + */ + public void record(DefaultData defaultData) { + if (isRecordingOn() && storageWriter.isWritingOn()) { + Collection recordingDataProcessors = recordingProperties.getRecordingDataProcessors(); + if (CollectionUtils.isNotEmpty(recordingDataProcessors)) { + for (AbstractDataProcessor dataProcessor : recordingDataProcessors) { + dataProcessor.process(defaultData); + } + } + involvedAgentsSet.add(defaultData.getPlatformIdent()); + + } + } + + /** + * Starts or schedules the recording. If the recording properties define the start date that is + * after current time, recording will be scheduled. Otherwise it is started right away. + * + * @param stWriter + * Writer for executing writing tasks. + * @param recProperties + * {@link RecordingProperties} used during the recording. + * @throws StorageException + * If parameters are null, storage writer is not prepared for write or data + * processor are not provided. + */ + public synchronized void startOrScheduleRecording(final StorageWriter stWriter, final RecordingProperties recProperties) throws StorageException { + if (!isRecordingOn() && !isRecordingScheduled()) { + if (null == stWriter) { + throw new StorageException("Storage writer can not be null. Recording will not be started."); + } else if (!stWriter.isWritingOn()) { + throw new StorageException("Storage writer must be prepared for write. Recording will not be started."); + } else if (null == recProperties) { + throw new StorageException("Recording properties can not be null. Recording will not be started."); + } else if (CollectionUtils.isEmpty(recProperties.getRecordingDataProcessors())) { + throw new StorageException("Recording data processor must be provided for recording."); + } + storageWriter = stWriter; + recordingProperties = recProperties; + + long startDelay = recProperties.getStartDelay(); + if (startDelay > 0) { + recordingState = RecordingState.SCHEDULED; + Runnable startRecordingRunnable = new Runnable() { + @Override + public void run() { + CmrStorageRecorder.this.startRecording(storageWriter, recordingProperties); + } + }; + startRecordingFuture = executorService.schedule(startRecordingRunnable, startDelay, TimeUnit.MILLISECONDS); + Date recordStartDate = new Date(System.currentTimeMillis() + startDelay); + recordingProperties.setRecordStartDate(recordStartDate); + } else { + startRecording(storageWriter, recordingProperties); + } + + } + } + + /** + * Prepares the writer for recording by passing the data processors that will be used when + * {@link #record(DefaultData)} is called. + * + * @param stWriter + * Writer for executing writing tasks. + * @param recProperties + * {@link RecordingProperties} used during the recording. + */ + private synchronized void startRecording(StorageWriter stWriter, RecordingProperties recProperties) { + if (!isRecordingOn()) { + storageWriter = stWriter; + recordingProperties = recProperties; + + // prepare the processors if they are given + Collection recordingDataProcessors = recordingProperties.getRecordingDataProcessors(); + if (null != recordingDataProcessors) { + for (AbstractDataProcessor abstractDataProcessor : recordingDataProcessors) { + abstractDataProcessor.setStorageWriter(storageWriter); + } + } + + // update state + recordingState = RecordingState.ON; + + // set start and end dates + recordingProperties.setRecordStartDate(new Date()); + + // set the task for stopping the recording if stop date is provided + long recordingDuration = recordingProperties.getRecordDuration(); + if (recordingDuration > 0) { + Runnable stopRecordingRunnable = new Runnable() { + @Override + public void run() { + try { + cmrStorageManager.stopRecording(); + } catch (Exception e) { + log.warn("Automatic stop of recording failed for the storage: " + getStorageData(), e); + } + } + }; + + stopRecordingFuture = executorService.schedule(stopRecordingRunnable, recordingDuration, TimeUnit.MILLISECONDS); + Date recordEndDate = new Date(System.currentTimeMillis() + recordingDuration); + recordingProperties.setRecordEndDate(recordEndDate); + } + + if (log.isDebugEnabled()) { + log.info("Recording started for storage: " + getStorageData()); + } + } + } + + /** + * Stops recording by flushing all the recording processors. + */ + public synchronized void stopRecording() { + if (isRecordingOn()) { + if (null != stopRecordingFuture) { + if (!stopRecordingFuture.isDone() && !stopRecordingFuture.isCancelled()) { + stopRecordingFuture.cancel(false); + } + stopRecordingFuture = null; // NOPMD + } + + Collection recordingDataProcessors = recordingProperties.getRecordingDataProcessors(); + if (null != recordingDataProcessors) { + for (AbstractDataProcessor abstractDataProcessor : recordingDataProcessors) { + abstractDataProcessor.flush(); + } + } + + // save system info data if necessary + if (!involvedAgentsSet.isEmpty()) { + List toRecord = storageDataDao.getSystemInformationData(involvedAgentsSet); + for (SystemInformationData defaultData : toRecord) { + record(defaultData); + } + } + involvedAgentsSet.clear(); + + if (log.isDebugEnabled()) { + log.info("Recording stopped for storage: " + getStorageData()); + } + } else if (isRecordingScheduled()) { + if (null != startRecordingFuture) { + if (!startRecordingFuture.isDone() && !startRecordingFuture.isCancelled()) { + startRecordingFuture.cancel(false); + } + startRecordingFuture = null; // NOPMD + } + } + + storageWriter = null; // NOPMD + recordingProperties = null; // NOPMD + recordingState = RecordingState.OFF; + } + + /** + * Is recording active. The recording is active only when the {@link #storageWriter} + * {@link #recordingProperties} are set. + * + * @return True if the recording is active. + */ + public boolean isRecordingOn() { + return recordingState == RecordingState.ON; + } + + /** + * Is recording scheduled. + * + * @return True if the recording is scheduled. + */ + public boolean isRecordingScheduled() { + return recordingState == RecordingState.SCHEDULED; + } + + /** + * Returns the {@link StorageData} that is used for recording. + * + * @return Returns the {@link StorageData} that is used for recording. + */ + protected StorageData getStorageData() { + return storageWriter.getStorageData(); + } + + /** + * Gets {@link #recordingState}. + * + * @return {@link #recordingState} + */ + public RecordingState getRecordingState() { + return recordingState; + } + + /** + * @return the storageWriter + */ + public StorageWriter getStorageWriter() { + return storageWriter; + } + + /** + * @return the recordingProperties + */ + public RecordingProperties getRecordingProperties() { + return recordingProperties; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/storage/CmrStorageWriter.java b/CMR/src/info/novatec/inspectit/cmr/storage/CmrStorageWriter.java new file mode 100644 index 000000000..02008a76b --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/storage/CmrStorageWriter.java @@ -0,0 +1,163 @@ +package info.novatec.inspectit.cmr.storage; + +import info.novatec.inspectit.cmr.dao.impl.PlatformIdentDaoImpl; +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.spring.logger.Log; +import info.novatec.inspectit.storage.StorageFileType; +import info.novatec.inspectit.storage.StorageWriter; +import info.novatec.inspectit.storage.label.ObjectStorageLabel; +import info.novatec.inspectit.storage.label.type.impl.DataTimeFrameLabelType; +import info.novatec.inspectit.util.TimeFrame; + +import java.io.IOException; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicLong; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * {@link StorageWriter} implementation for the CMR. + * + * @author Ivan Senic + * + */ +@Component("cmrStorageWriter") +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +@Lazy +public class CmrStorageWriter extends StorageWriter { + + /** + * The log of this class. + */ + @Log + Logger log; + + /** + * Set of involved Agents, used after recording to store proper Agent information. + */ + private Set involvedAgentsSet = new HashSet(); + + /** + * {@link AtomicLong} holding the time-stamp value of the oldest data written in the storage. + */ + private AtomicLong oldestDataTimestamp = new AtomicLong(Long.MAX_VALUE); + + /** + * {@link AtomicLong} holding the time-stamp value of the newest data written in the storage. + */ + private AtomicLong newestDataTimestamp = new AtomicLong(0); + + /** + * Platform ident dao. + */ + @Autowired + private PlatformIdentDaoImpl platformIdentDao; + + /** + * {@inheritDoc} + */ + @Override + public Future write(DefaultData defaultData) { + Future future = super.write(defaultData); + postWriteOperations(defaultData); + return future; + } + + /** + * {@inheritDoc} + */ + @Override + public Future write(DefaultData defaultData, Map kryoPreferences) { + Future future = super.write(defaultData, kryoPreferences); + postWriteOperations(defaultData); + return future; + } + + /** + * {@inheritDoc} + * + * @throws IOException + */ + protected void writeAgentData() throws IOException { + List involvedPlatformIdents = platformIdentDao.findAllInitialized(involvedAgentsSet); + for (PlatformIdent agent : involvedPlatformIdents) { + super.writeNonDefaultDataObject(agent, agent.getId() + StorageFileType.AGENT_FILE.getExtension()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void finalizeWrite() { + try { + writeAgentData(); + } catch (IOException e) { + log.error("Exception trying to write agent data to disk.", e); + } + super.finalizeWrite(); + + if (newestDataTimestamp.get() > 0 && oldestDataTimestamp.get() < Long.MAX_VALUE) { + TimeFrame timeFrame = new TimeFrame(new Date(oldestDataTimestamp.get()), new Date(newestDataTimestamp.get())); + ObjectStorageLabel timeframeLabel = new ObjectStorageLabel(timeFrame, new DataTimeFrameLabelType()); + getStorageData().addLabel(timeframeLabel, true); + } + } + + /** + * Executes post write operations: + * + *

    + *
  • Remembers the platform id of the written data + *
  • Updates the {@link #newestDataTimestamp} and the {@link #oldestDataTimestamp} if needed. + *
+ * + * @param defaultData + * {@link DefaultData} that has been written. + */ + private void postWriteOperations(DefaultData defaultData) { + involvedAgentsSet.add(defaultData.getPlatformIdent()); + + while (true) { + long oldestData = oldestDataTimestamp.get(); + if (oldestData > defaultData.getTimeStamp().getTime()) { + if (oldestDataTimestamp.compareAndSet(oldestData, defaultData.getTimeStamp().getTime())) { + break; + } + } else { + break; + } + } + + while (true) { + long newestData = newestDataTimestamp.get(); + if (newestData < defaultData.getTimeStamp().getTime()) { + if (newestDataTimestamp.compareAndSet(newestData, defaultData.getTimeStamp().getTime())) { + break; + } + } else { + break; + } + } + } + + /** + * @param platformIdentDao + * the platformIdentDao to set + */ + public void setPlatformIdentDao(PlatformIdentDaoImpl platformIdentDao) { + this.platformIdentDao = platformIdentDao; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/storage/CmrStorageWriterProvider.java b/CMR/src/info/novatec/inspectit/cmr/storage/CmrStorageWriterProvider.java new file mode 100644 index 000000000..46a9abeac --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/storage/CmrStorageWriterProvider.java @@ -0,0 +1,16 @@ +package info.novatec.inspectit.cmr.storage; + +/** + * Class for providing instances of {@link CmrStorageWriter} when needed. + * + * @author Ivan Senic + * + */ +public abstract class CmrStorageWriterProvider { + + /** + * @return Returns properly initialized {@link CmrStorageWriter}. + */ + protected abstract CmrStorageWriter getCmrStorageWriter(); + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/util/AgentStatusDataProvider.java b/CMR/src/info/novatec/inspectit/cmr/util/AgentStatusDataProvider.java new file mode 100644 index 000000000..f8beae760 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/util/AgentStatusDataProvider.java @@ -0,0 +1,94 @@ +package info.novatec.inspectit.cmr.util; + +import info.novatec.inspectit.communication.data.cmr.AgentStatusData; +import info.novatec.inspectit.communication.data.cmr.AgentStatusData.AgentConnection; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.stereotype.Component; + +/** + * Bean that saves the time when the last time platform ident received the data. + * + * @author Ivan Senic + * + */ +@Component +public class AgentStatusDataProvider { + + /** + * Map that holds IDs of the platform idents and {@link AgentStatusData} objects. + */ + private ConcurrentHashMap agentStatusDataMap = new ConcurrentHashMap(8, 0.75f, 1); + + /** + * Registers that the agent was connected. + * + * @param platformIdent + * ID of the platform ident. + */ + public void registerConnected(long platformIdent) { + AgentStatusData agentStatusData = agentStatusDataMap.get(platformIdent); + if (null == agentStatusData) { + agentStatusData = new AgentStatusData(AgentConnection.CONNECTED); + AgentStatusData existing = agentStatusDataMap.putIfAbsent(platformIdent, agentStatusData); + if (null != existing) { + agentStatusData = existing; + } + } + agentStatusData.setAgentConnection(AgentConnection.CONNECTED); + } + + /** + * Registers that the agent has been disconnected. + * + * @param platformIdent + * ID of the platform ident. + */ + public void registerDisconnected(long platformIdent) { + AgentStatusData agentStatusData = agentStatusDataMap.get(platformIdent); + if (null != agentStatusData) { + agentStatusData.setAgentConnection(AgentConnection.DISCONNECTED); + } + } + + /** + * Registers the time when last data was received for a given platform ident. + * + * @param platformIdent + * ID of the platform ident. + */ + public void registerDataSent(long platformIdent) { + AgentStatusData agentStatusData = agentStatusDataMap.get(platformIdent); + if (null != agentStatusData) { + agentStatusData.setLastDataSendTimestamp(System.currentTimeMillis()); + } + } + + /** + * Informs the {@link AgentStatusDataProvider} that the platform has been deleted from the CMR. + * All kept information will be deleted. + * + * @param platformId + * ID of the platform ident. + */ + public void registerDeleted(long platformId) { + agentStatusDataMap.remove(platformId); + } + + /** + * @return Returns the map of platform ident IDs and dates when the last data was received. + */ + public Map getAgentStatusDataMap() { + long currentTime = System.currentTimeMillis(); + Map map = new HashMap(); + for (Entry entry : agentStatusDataMap.entrySet()) { + entry.getValue().setServerTimestamp(currentTime); + map.put(entry.getKey(), entry.getValue()); + } + return map; + } +} diff --git a/CMR/src/info/novatec/inspectit/cmr/util/CacheIdGenerator.java b/CMR/src/info/novatec/inspectit/cmr/util/CacheIdGenerator.java new file mode 100644 index 000000000..5cf429b85 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/util/CacheIdGenerator.java @@ -0,0 +1,35 @@ +package info.novatec.inspectit.cmr.util; + +import info.novatec.inspectit.communication.DefaultData; + +import java.util.concurrent.atomic.AtomicLong; + +import org.springframework.stereotype.Component; + +/** + * Class that generates the ID for the objects that reside in cache. The ID can be generated either + * for the objects that are going to the indexing structure, or to the elements that go to the + * buffer. + * + * @author Ivan Senic + * + */ +@Component +public class CacheIdGenerator { + + /** + * Atomic long to keep next available id. + */ + private AtomicLong nextId = new AtomicLong(Long.MAX_VALUE / 2); + + /** + * Assigns the {@link DefaultData} object a unique ID. + * + * @param defaultData + * Object to assign the ID for. + */ + public void assignObjectAnId(DefaultData defaultData) { + long id = nextId.incrementAndGet(); + defaultData.setId(id); + } +} diff --git a/CMR/src/info/novatec/inspectit/cmr/util/Converter.java b/CMR/src/info/novatec/inspectit/cmr/util/Converter.java new file mode 100644 index 000000000..706699ec9 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/util/Converter.java @@ -0,0 +1,33 @@ +package info.novatec.inspectit.cmr.util; + +/** + * Converter utility class. + * + * @author Patrice Bouillet + * + */ +public final class Converter { + + /** + * Nano seconds to milliseconds value. + */ + private static final double NANO_TO_MS_VALUE = 1000000.0d; + + /** + * Private constructor prevents instantiation. This is just a utility class. + */ + private Converter() { + } + + /** + * Converts the nano seconds into milliseconds. + * + * @param nanoTime + * The nano time. + * @return Returns the milliseconds. + */ + public static double nanoToMilliseconds(long nanoTime) { + return nanoTime / NANO_TO_MS_VALUE; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/util/ExceptionEventType.java b/CMR/src/info/novatec/inspectit/cmr/util/ExceptionEventType.java new file mode 100644 index 000000000..03c21363d --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/util/ExceptionEventType.java @@ -0,0 +1,132 @@ +package info.novatec.inspectit.cmr.util; + +import info.novatec.inspectit.communication.ExceptionEvent; + +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; + +import org.hibernate.HibernateException; +import org.hibernate.usertype.UserType; + +/** + * User type for Hibernate to map the {@link ExceptionEvent} class into the database. + * + * @author Patrice Bouillet + * + */ +public class ExceptionEventType implements UserType { + + /** + * The sql types. + */ + private static final int[] TYPES = new int[] { Types.NUMERIC }; + + /** + * {@inheritDoc} + */ + @Override + public int[] sqlTypes() { + return TYPES.clone(); + } + + /** + * {@inheritDoc} + */ + @Override + public Object deepCopy(Object value) { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isMutable() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner) throws HibernateException, SQLException { + ExceptionEvent result = null; + int dbValue = resultSet.getInt(names[0]); + if (dbValue != -1) { + result = ExceptionEvent.fromOrd(dbValue); + } + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public void nullSafeSet(PreparedStatement statement, Object value, int index) throws HibernateException, SQLException { + if (null == value) { + statement.setInt(index, -1); + } else { + ExceptionEvent event = (ExceptionEvent) value; + int dbValue = event.ordinal(); + statement.setInt(index, dbValue); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Object replace(Object arg0, Object arg1, Object arg2) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public Class returnedClass() { + return ExceptionEvent.class; + } + + /** + * {@inheritDoc} + */ + @Override + public Object assemble(Serializable arg0, Object arg1) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public Serializable disassemble(Object arg0) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object x, Object y) { // NOPMD + if (x == y) { // NOPMD + return true; + } else if (x == null || y == null) { + return false; + } else { + return x.equals(y); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode(Object object) { + return object.hashCode(); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/util/HealthStatus.java b/CMR/src/info/novatec/inspectit/cmr/util/HealthStatus.java new file mode 100644 index 000000000..ce141afb3 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/util/HealthStatus.java @@ -0,0 +1,495 @@ +package info.novatec.inspectit.cmr.util; + +import info.novatec.inspectit.cmr.cache.IBuffer; +import info.novatec.inspectit.cmr.service.AgentStorageService; +import info.novatec.inspectit.cmr.service.ICmrManagementService; +import info.novatec.inspectit.cmr.storage.CmrStorageManager; +import info.novatec.inspectit.spring.logger.Log; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.nio.ByteBufferProvider; +import info.novatec.inspectit.storage.nio.write.WritingChannelManager; +import info.novatec.inspectit.storage.recording.RecordingState; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryUsage; +import java.lang.management.OperatingSystemMXBean; +import java.lang.management.RuntimeMXBean; +import java.lang.management.ThreadMXBean; +import java.util.Map; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * This service is used to check the health of the CMR in terms of cpu, memory, some overall + * statistics etc. + * + * @author Patrice Bouillet + * + */ +@Component +public class HealthStatus { + + /** The logger of this class. */ + @Log + Logger log; + + /** + * For the visualization of the memory and load average, a graphical visualization is put into + * the log for easier analysis. This char is used as the start and the end of the printed lines. + */ + private static final char START_END_CHAR = '+'; + + /** + * The width of the visualization of the memory and load average. + */ + private static final int WIDTH = 30; + + /** + * The fixed rate of the refresh rate for gathering the statistics. + */ + private static final int FIXED_RATE = 60000; + + /** + * Are the beans that are responsible for creating the Health Status available. + */ + private boolean beansAvailable = false; + + /** + * The memory mx bean used to extract information about the memory of the system. + */ + private MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + + /** + * The operating system mx bean. + */ + private OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean(); + + /** + * The thread mx bean. + */ + private ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + + /** + * The runtime mx bean. + */ + private RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); + + /** + * Buffer that reports status. + */ + @Autowired + private IBuffer buffer; + + /** + * {@link AgentStorageService} for reporting the amount of dropped data on the CMR. + */ + @Autowired + private ICmrManagementService cmrManagementService; + + /** + * {@link WritingChannelManager} for status of IO tasks. + */ + @Autowired + private WritingChannelManager writingChannelManager; + + /** + * Storage manager for status of writing tasks. + */ + @Autowired + private CmrStorageManager storageManager; + + /** + * Byte buffer provider for the buffers pool status. + */ + @Autowired + private ByteBufferProvider byteBufferProvider; + + /** + * Log all the statistics. + */ + @Scheduled(fixedRate = FIXED_RATE) + public void logStatistics() { + if (beansAvailable) { + if (log.isInfoEnabled()) { + logOperatingSystemStatistics(); + logRuntimeStatistics(); + logMemoryStatistics(); + logThreadStatistics(); + log.info("\n"); + } + } + if (log.isInfoEnabled()) { + logDroppedData(); + logBufferStatistics(); + logStorageStatistics(); + } + } + + /** + * Log the operating system statistics. + */ + private void logOperatingSystemStatistics() { + String arch = operatingSystemMXBean.getArch(); + String name = operatingSystemMXBean.getName(); + String version = operatingSystemMXBean.getVersion(); + int availCpus = operatingSystemMXBean.getAvailableProcessors(); + double loadAverage = operatingSystemMXBean.getSystemLoadAverage(); + + StringBuilder sb = new StringBuilder(); + sb.append("System: "); + sb.append(name); + sb.append(' '); + sb.append(version); + sb.append(' '); + sb.append(arch); + sb.append(" ("); + sb.append(availCpus); + sb.append(" cpu(s) load average: "); + sb.append(loadAverage); + log.info(sb.toString()); + + logGraphicalLoadAverage(loadAverage, availCpus); + } + + /** + * Log a graphical version of the load average. + * + * @param loadAvg + * The current load average over the last 60 seconds. + * @param availCpus + * The available cpus. + * + * @see OperatingSystemMXBean#getSystemLoadAverage() + */ + private void logGraphicalLoadAverage(double loadAvg, int availCpus) { + double loadAverage = loadAvg; + if (loadAverage < 0) { + loadAverage = 0; + } + double value = (double) WIDTH / availCpus; + long load = Math.round(loadAverage * value); + if (load > WIDTH) { + // Necessary so that we don't brake the limit in graphical representation + load = WIDTH; + } + String title = "CPU load"; + + // print first line + StringBuilder sb = new StringBuilder(); + sb.append(START_END_CHAR); + sb.append('-'); + sb.append(title); + for (int i = title.length() + 1; i < WIDTH; i++) { + sb.append('-'); + } + sb.append(START_END_CHAR); + log.info(sb.toString()); + + // now create the middle line with the status. + sb = new StringBuilder(); + sb.append(START_END_CHAR); + for (int i = 0; i < load; i++) { + sb.append('/'); + } + // now fill up the remaining space + for (long i = load; i < WIDTH; i++) { + sb.append(' '); + } + sb.append(START_END_CHAR); + log.info(sb.toString()); + + // print last line + sb = new StringBuilder(); + sb.append(START_END_CHAR); + for (int i = 0; i < WIDTH; i++) { + sb.append('-'); + } + sb.append(START_END_CHAR); + log.info(sb.toString()); + } + + /** + * Log the runtime statistics. + */ + private void logRuntimeStatistics() { + String name = runtimeMXBean.getName(); + // String specName = runtimeMXBean.getSpecName(); + // String specVendor = runtimeMXBean.getSpecVendor(); + // String specVersion = runtimeMXBean.getSpecVersion(); + long uptime = runtimeMXBean.getUptime(); + String vmName = runtimeMXBean.getVmName(); + String vmVendor = runtimeMXBean.getVmVendor(); + + StringBuilder sb = new StringBuilder(); + sb.append("VM: "); + sb.append(vmName); + sb.append(" ("); + sb.append(vmVendor); + sb.append(") process: "); + sb.append(name); + sb.append(" uptime: "); + sb.append(uptime); + sb.append(" ms"); + log.info(sb.toString()); + } + + /** + * Log the memory statistics. + */ + private void logMemoryStatistics() { + MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage(); + MemoryUsage nonHeapMemoryUsage = memoryMXBean.getNonHeapMemoryUsage(); + + log.info("Heap: " + heapMemoryUsage); + logGraphicalMemoryUsage(heapMemoryUsage, "Heap"); + log.info("Non Heap: " + nonHeapMemoryUsage); + logGraphicalMemoryUsage(nonHeapMemoryUsage, "Non-Heap"); + log.info("Pending finalizations: " + memoryMXBean.getObjectPendingFinalizationCount()); + } + + /** + * Log a graphical version of the memory usage object.. + * + * @param memoryUsage + * The memory usage object to log. + * @param title + * Title of graphical box. + * + * @see MemoryUsage + */ + private void logGraphicalMemoryUsage(MemoryUsage memoryUsage, String title) { + if (areMemoryUsageValuesCorrect(memoryUsage)) { + double value = (double) WIDTH / memoryUsage.getMax(); + long used = Math.round(memoryUsage.getUsed() * value); + long committed = Math.round(memoryUsage.getCommitted() * value); + + // print first line + StringBuilder sb = new StringBuilder(); + sb.append(START_END_CHAR); + sb.append('-'); + sb.append(title); + for (int i = title.length() + 1; i < WIDTH; i++) { + sb.append('-'); + } + + sb.append(START_END_CHAR); + log.info(sb.toString()); + + // now create the middle line with the status. + sb = new StringBuilder(); + sb.append(START_END_CHAR); + for (int i = 0; i < used; i++) { + sb.append('/'); + } + long pos = used; + if (pos <= committed) { + // only print the char if committed is greater or equal than the + // current position. + for (long i = pos; i < committed; i++) { + sb.append(' '); + } + sb.append('|'); + pos = committed + 1L; + } + // now fill up the remaining space + for (long i = pos; i < WIDTH; i++) { + sb.append(' '); + } + + // only print last char if committed is smaller + if (committed < WIDTH) { + sb.append(START_END_CHAR); + } + log.info(sb.toString()); + + // print last line + sb = new StringBuilder(); + sb.append(START_END_CHAR); + for (int i = 0; i < WIDTH; i++) { + sb.append('-'); + } + sb.append(START_END_CHAR); + log.info(sb.toString()); + } + } + + /** + * Checks if the values in {@link MemoryUsage} are OK for the graphical memory logging. + * + * @param memoryUsage + * {@link MemoryUsage} + * @return True if values are OK. + */ + private boolean areMemoryUsageValuesCorrect(MemoryUsage memoryUsage) { + if (memoryUsage.getCommitted() < 0 || memoryUsage.getUsed() < 0 || memoryUsage.getMax() < 0) { + return false; + } + if (memoryUsage.getUsed() > memoryUsage.getMax()) { + return false; + } + if (memoryUsage.getUsed() > memoryUsage.getCommitted()) { + return false; + } + if (memoryUsage.getCommitted() > memoryUsage.getMax()) { + return false; + } + return true; + } + + /** + * Log the thread statistics. + */ + private void logThreadStatistics() { + int threadCount = threadMXBean.getThreadCount(); + long totalStartedThreads = threadMXBean.getTotalStartedThreadCount(); + + StringBuilder sb = new StringBuilder(); + sb.append("Threads: "); + sb.append(threadCount); + sb.append(" total started: "); + sb.append(totalStartedThreads); + log.info(sb.toString()); + } + + /** + * Log buffer statistic. + */ + private void logBufferStatistics() { + String[] lines = buffer.toString().split("\n"); + for (String str : lines) { + log.info(str); + } + logGraphicalBufferOccupancy(buffer.getOccupancyPercentage()); + } + + /** + * Log a graphical version of buffer occupancy. + * + * @param bufferOccupancy + * Current buffer occupancy in percentages. + */ + private void logGraphicalBufferOccupancy(float bufferOccupancy) { + String title = "Buffer"; + int used = (int) (bufferOccupancy * WIDTH); + + // print first line + StringBuilder sb = new StringBuilder(); + sb.append(START_END_CHAR); + sb.append('-'); + sb.append(title); + for (int i = title.length() + 1; i < WIDTH; i++) { + sb.append('-'); + } + sb.append(START_END_CHAR); + log.info(sb.toString()); + + // now create the middle line with the status. + sb = new StringBuilder(); + sb.append(START_END_CHAR); + for (int i = 0; i < used; i++) { + sb.append('/'); + } + for (int j = used; j < WIDTH; j++) { + sb.append(' '); + } + sb.append(START_END_CHAR); + log.info(sb.toString()); + + // print last line + sb = new StringBuilder(); + sb.append(START_END_CHAR); + for (int i = 0; i < WIDTH; i++) { + sb.append('-'); + } + sb.append(START_END_CHAR); + log.info(sb.toString()); + } + + /** + * Logs the storage stats. + */ + private void logStorageStatistics() { + log.info("Status of the Write Channel Manager's executor service: " + writingChannelManager.getExecutorServiceStatus()); + log.info("Status of each writable storage and its executor service:"); + Map writersStatusMap = storageManager.getWritersStatus(); + if (!writersStatusMap.isEmpty()) { + for (Map.Entry entry : writersStatusMap.entrySet()) { + log.info("Storage " + entry.getKey() + " - " + entry.getValue()); + } + } else { + log.info("No active writable storage available."); + } + + if (storageManager.getRecordingState() == RecordingState.ON) { + StorageData recordingStorageData = storageManager.getRecordingStorage(); + if (null != recordingStorageData) { + log.info("Recording is active on the storage " + recordingStorageData + "."); + } + } else { + log.info("Recording is not active."); + } + + log.info("Byte buffer provider has " + byteBufferProvider.getBufferPoolSize() + " available buffers in the pool with total capacity of " + byteBufferProvider.getAvailableCapacity() + + " bytes. Total created capacity of the pool is " + byteBufferProvider.getCreatedCapacity() + " bytes."); + } + + /** + * Logs the amount of dropped data on CMR. + */ + private void logDroppedData() { + log.info("Dropped elements due to the high load on the CMR (total count): " + cmrManagementService.getDroppedDataCount()); + } + + /** + * Checks if the beans are available and sets the {@link #beansAvailable} depending on the + * result of check. + */ + private void startUpCheck() { + try { + operatingSystemMXBean.getArch(); + operatingSystemMXBean.getName(); + operatingSystemMXBean.getVersion(); + operatingSystemMXBean.getAvailableProcessors(); + operatingSystemMXBean.getSystemLoadAverage(); + + runtimeMXBean.getName(); + runtimeMXBean.getUptime(); + runtimeMXBean.getVmName(); + runtimeMXBean.getVmVendor(); + + memoryMXBean.getHeapMemoryUsage(); + memoryMXBean.getNonHeapMemoryUsage(); + + threadMXBean.getThreadCount(); + threadMXBean.getTotalStartedThreadCount(); + + beansAvailable = true; + } catch (Exception e) { + beansAvailable = false; + } + } + + /** + * {@inheritDoc} + */ + @PostConstruct + public void postConstruct() throws Exception { + startUpCheck(); + if (beansAvailable) { + if (log.isInfoEnabled()) { + log.info("Health Service active..."); + } + } else { + if (log.isInfoEnabled()) { + log.info("Health Service not active..."); + } + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/util/HibernateUtil.java b/CMR/src/info/novatec/inspectit/cmr/util/HibernateUtil.java new file mode 100644 index 000000000..d87534a9d --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/util/HibernateUtil.java @@ -0,0 +1,81 @@ +package info.novatec.inspectit.cmr.util; + +import info.novatec.inspectit.util.IHibernateUtil; + +import org.apache.commons.lang.ArrayUtils; +import org.hibernate.Hibernate; +import org.hibernate.collection.PersistentCollection; +import org.hibernate.collection.PersistentList; +import org.hibernate.collection.PersistentMap; +import org.hibernate.collection.PersistentSet; +import org.hibernate.proxy.HibernateProxy; +import org.springframework.stereotype.Component; + +/** + * Our own Hibernate utility class. + * + * @author Ivan Senic + * + */ +@Component +public class HibernateUtil implements IHibernateUtil { + + /** + * {@inheritDoc} + */ + public boolean isInitialized(Object proxy) { + return Hibernate.isInitialized(proxy); + } + + /** + * {@inheritDoc} + */ + public boolean isPersistentCollection(Class collectionClass) { + return PersistentCollection.class.isAssignableFrom(collectionClass); + } + + /** + * {@inheritDoc} + */ + public boolean isPersistentMap(Class collectionClass) { + return PersistentMap.class.isAssignableFrom(collectionClass); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPersistentList(Class collectionClass) { + return PersistentList.class.isAssignableFrom(collectionClass); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPersistentSet(Class collectionClass) { + return PersistentSet.class.isAssignableFrom(collectionClass); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isProxy(Class proxyClass) { + return ArrayUtils.contains(proxyClass.getInterfaces(), HibernateProxy.class); + } + + /** + * {@inheritDoc} + */ + @Override + public Object getUnproxiedObject(Object proxy) { + // getImplementation will try to initialize the object + // but our objects should already be initialized, thus should work with no problem + if (proxy instanceof HibernateProxy) { + return ((HibernateProxy) proxy).getHibernateLazyInitializer().getImplementation(); + } + return proxy; + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/util/ListStringType.java b/CMR/src/info/novatec/inspectit/cmr/util/ListStringType.java new file mode 100644 index 000000000..a08ae95a5 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/util/ListStringType.java @@ -0,0 +1,171 @@ +package info.novatec.inspectit.cmr.util; + +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Arrays; +import java.util.List; + +import org.hibernate.type.StandardBasicTypes; +import org.hibernate.usertype.UserType; + +/** + * Additional type for Hibernate which maps an arbitrary List to a String in such a way that it + * concatenates the List with a whitespace (by calling {@link #toString()} on every Object). + *

+ * Example:
+ *

    + *
  • java.lang.String
  • + *
  • int
  • + *
  • java.lang.Object[]
  • + *
+ * leads to: java.lang.String int java.lang.Object[]
+ * and vice versa. + * + * @author Patrice Bouillet + * + */ +public class ListStringType implements UserType { + + /** + * The sql types. + */ + private static final int[] TYPES = new int[] { Types.VARCHAR }; + + /** + * {@inheritDoc} + */ + public Object assemble(Serializable arg0, Object arg1) { + return deepCopy(arg0); + + } + + /** + * {@inheritDoc} + */ + public Object deepCopy(Object x) { + return x; + } + + /** + * {@inheritDoc} + */ + public Serializable disassemble(Object value) { + return (Serializable) deepCopy(value); + } + + /** + * {@inheritDoc} + */ + public boolean equals(Object x, Object y) { // NOPMD + if (x == y) { // NOPMD + return true; + } + + if (x == null) { + return false; + } + + return x.equals(y); + } + + /** + * {@inheritDoc} + */ + public int hashCode(Object object) { + return object.hashCode(); + } + + /** + * {@inheritDoc} + */ + public boolean isMutable() { + return false; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings({ "deprecation" }) + public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws SQLException { + String dbValue = (String) StandardBasicTypes.STRING.nullSafeGet(rs, names[0]); + + if (dbValue != null) { + return explode(dbValue); + } else { + return null; + } + + } + + /** + * Explodes the given String by splitting it up. The split char is just a whitespace. + * + * @param dbValue + * The String to explode + * @return The exploded String. + */ + private List explode(String dbValue) { + return Arrays.asList(dbValue.split(" ")); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings({ "deprecation" }) + public void nullSafeSet(PreparedStatement st, Object value, int index) throws SQLException { + if (value != null) { + String v = concat((List) value); + StandardBasicTypes.STRING.nullSafeSet(st, v, index); + } else { + StandardBasicTypes.STRING.nullSafeSet(st, null, index); + } + } + + /** + * Concatenates the given list with the whitespace char and returns the generated String. + * + * @param list + * The list to concatenate. + * @return The generated String out of the list. + */ + private String concat(List list) { + StringBuffer buffer = new StringBuffer(); + + if (!list.isEmpty()) { + buffer.append(list.get(0).toString()); + + for (int i = 1; i < list.size(); i++) { + Object object = list.get(i); + buffer.append(' '); + buffer.append(object); + } + } + + return buffer.toString(); + } + + /** + * {@inheritDoc} + */ + public Object replace(Object arg0, Object arg1, Object arg2) { + return deepCopy(arg0); + } + + /** + * {@inheritDoc} + */ + public Class returnedClass() { + return List.class; + } + + /** + * {@inheritDoc} + */ + public int[] sqlTypes() { + return TYPES.clone(); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/util/MapStringType.java b/CMR/src/info/novatec/inspectit/cmr/util/MapStringType.java new file mode 100644 index 000000000..fc37a2b38 --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/util/MapStringType.java @@ -0,0 +1,187 @@ +package info.novatec.inspectit.cmr.util; + +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang.StringUtils; +import org.hibernate.HibernateException; +import org.hibernate.type.StandardBasicTypes; +import org.hibernate.usertype.UserType; + +/** + * Additional type for Hibernate which maps an arbitrary Map to a String in such a way that it + * concatenates the Map entries with a {@value #DELIMITER} (by calling {@link #toString()} on every + * Object). + * + * @author Ivan Senic + * + */ +public class MapStringType implements UserType { + + /** + * Delimiter for key and value pairs. + */ + private static final String DELIMITER = "~:~"; + + /** + * The sql types. + */ + private static final int[] TYPES = new int[] { Types.VARCHAR }; + + @Override + @SuppressWarnings("deprecation") + public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException { + String dbValue = (String) StandardBasicTypes.STRING.nullSafeGet(rs, names[0]); + + if (dbValue != null) { + return explode(dbValue); + } else { + return null; + } + } + + /** + * Creates map out of the string. + * + * @param dbValue + * String value from the database. + * @return {@link Map} + */ + private Object explode(String dbValue) { + if (StringUtils.isNotEmpty(dbValue)) { + Map map = new HashMap<>(); + String[] splitted = dbValue.split(DELIMITER); + for (int i = 0; i < splitted.length; i += 2) { + map.put(splitted[i], splitted[i + 1]); + } + return map; + } else { + return Collections.emptyMap(); + } + } + + @Override + @SuppressWarnings("deprecation") + public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException { + if (value != null) { + String v = concat((Map) value); + StandardBasicTypes.STRING.nullSafeSet(st, v, index); + } else { + StandardBasicTypes.STRING.nullSafeSet(st, null, index); + } + + } + + /** + * Transforms map to a string. + * + * @param value + * map to transform. + * @return String representation of map. + */ + private String concat(Map value) { + StringBuilder stringBuilder = new StringBuilder(); + if (MapUtils.isNotEmpty(value)) { + int i = 1; + for (Map.Entry entry : value.entrySet()) { + stringBuilder.append(entry.getKey().toString()); + stringBuilder.append(DELIMITER); + stringBuilder.append(entry.getValue()); + if (i < value.size()) { + stringBuilder.append(DELIMITER); + } + i++; + } + + } + return stringBuilder.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public Object deepCopy(Object value) throws HibernateException { + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isMutable() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public Serializable disassemble(Object value) throws HibernateException { + return (Serializable) deepCopy(value); + } + + /** + * {@inheritDoc} + */ + @Override + public Object assemble(Serializable cached, Object owner) throws HibernateException { + return deepCopy(cached); + } + + /** + * {@inheritDoc} + */ + @Override + public Object replace(Object original, Object target, Object owner) throws HibernateException { + return deepCopy(original); + } + + /** + * {@inheritDoc} + */ + @Override + public int[] sqlTypes() { + return TYPES.clone(); + } + + /** + * {@inheritDoc} + */ + @Override + public Class returnedClass() { + return Map.class; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object x, Object y) throws HibernateException { // NOPMD + if (x == y) { // NOPMD + return true; + } + + if (x == null) { + return false; + } + + return x.equals(y); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode(Object x) throws HibernateException { + return x.hashCode(); + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/util/PlatformIdentCache.java b/CMR/src/info/novatec/inspectit/cmr/util/PlatformIdentCache.java new file mode 100644 index 000000000..7d9dd2f7b --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/util/PlatformIdentCache.java @@ -0,0 +1,121 @@ +package info.novatec.inspectit.cmr.util; + +import info.novatec.inspectit.cmr.model.PlatformIdent; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.stereotype.Component; + +/** + * Bean for caching the {@link PlatformIdent} objects, so that they don't have to be loaded from the + * database all the time. + * + * @author Ivan Senic + * + */ +@Component +public class PlatformIdentCache { + + /** + * Clean set. + */ + private Map cleanPlatformIdents = new ConcurrentHashMap(); + + /** + * Dirty set. + */ + private Map dirtyPlatformIdents = new ConcurrentHashMap(); + + /** + * Marks platform ident dirty. + * + * @param platformIdent + * {@link PlatformIdent}. + */ + public void markDirty(PlatformIdent platformIdent) { + mark(platformIdent, true); + } + + /** + * Marks platform ident clean. If the marker with this {@link PlatformIdent} already exists, its + * {@link PlatformIdent} object will be changed with the supplied clean one. + * + * @param platformIdent + * {@link PlatformIdent}. + */ + public void markClean(PlatformIdent platformIdent) { + mark(platformIdent, false); + } + + /** + * Remove {@link PlatformIdent} from cache. + * + * @param platformIdent + * {@link PlatformIdent}. + */ + public void remove(PlatformIdent platformIdent) { + cleanPlatformIdents.remove(platformIdent.getId()); + dirtyPlatformIdents.remove(platformIdent.getId()); + } + + /** + * Returns clean {@link PlatformIdent}s. This one can be transfered to the UI directly. + * + * @return Returns clean {@link PlatformIdent}s. + */ + public Collection getCleanPlatformIdents() { + return getPlatformIdents(false); + } + + /** + * Returns dirty {@link PlatformIdent}s. This one can not be transfered to the UI. + * + * @return Returns dirty {@link PlatformIdent}s. + */ + public Collection getDirtyPlatformIdents() { + return getPlatformIdents(true); + } + + /** + * @return Returns number of {@link PlatformIdent} obejcts in the cache. + */ + public int getSize() { + return cleanPlatformIdents.size() + dirtyPlatformIdents.size(); + } + + /** + * Provides the list of clean or dirty {@link PlatformIdent}s. + * + * @param dirty + * Should idents be dirty or not. + * @return List of {@link PlatformIdent}s. + */ + private Collection getPlatformIdents(boolean dirty) { + if (dirty) { + return dirtyPlatformIdents.values(); + } else { + return cleanPlatformIdents.values(); + } + } + + /** + * Marks a {@link PlatformIdent} dirty or clean. + * + * @param platformIdent + * {@link PlatformIdent} to mark. + * @param dirty + * Is it dirty. + */ + private void mark(PlatformIdent platformIdent, boolean dirty) { + cleanPlatformIdents.remove(platformIdent.getId()); + dirtyPlatformIdents.remove(platformIdent.getId()); + if (dirty) { + dirtyPlatformIdents.put(platformIdent.getId(), platformIdent); + } else { + cleanPlatformIdents.put(platformIdent.getId(), platformIdent); + } + } + +} diff --git a/CMR/src/info/novatec/inspectit/cmr/util/ShutdownService.java b/CMR/src/info/novatec/inspectit/cmr/util/ShutdownService.java new file mode 100644 index 000000000..adaee061b --- /dev/null +++ b/CMR/src/info/novatec/inspectit/cmr/util/ShutdownService.java @@ -0,0 +1,157 @@ +package info.novatec.inspectit.cmr.util; + +import info.novatec.inspectit.cmr.CMR; +import info.novatec.inspectit.spring.logger.Log; + +import java.io.IOException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.springframework.stereotype.Component; + +/** + * This component can shutdown or restart the CMR. + * + * @author Ivan Senic + * + */ +@Component +public class ShutdownService { + + /** + * Code that JVM will exit if restart is required. Note that this code must be max in 8bits size + * and changing it requires the change of the CMR startup script. + */ + private static final int RESTART_EXIT_CODE = 10; + + /** + * Command for restarting the CMR on Windows machines. Note that this command is used only if + * the CMR was started as a Windows Service (Procrun). + */ + private static final String RESTART_CMR_COMMAND = "cmd.exe /c net stop inspectITCMR >NUL & net start inspectITCMR >NUL"; + + /** + * Command for shutting down the CMR on Windows machines. Note that this command is used only if + * the CMR was started as a Windows Service (Procrun). + */ + private static final String SHUTDOWN_CMR_COMMAND = "cmd.exe /c net stop inspectITCMR >NUL"; + + /** + * The logger of this class. + */ + @Log + Logger log; + + /** + * Flag for shutdown initialization. + */ + private volatile boolean isShutdown = false; + + /** + * Executor service for executing restart/shutdown. We need any thread created to be daemon. + * We'll use the executor to asynchronously restart so that the methods can return. + */ + private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1, new ThreadFactory() { + + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setDaemon(true); + return t; + } + }); + + /** + * {@inheritDoc} + */ + public void restart() { + synchronized (this) { + if (this.isShutdown) { + return; + } + this.isShutdown = true; + } + doShutdown(true); + } + + /** + * {@inheritDoc} + */ + public void shutdown() { + synchronized (this) { + if (this.isShutdown) { + return; + } + this.isShutdown = true; + } + doShutdown(false); + + } + + /** + * Executes shutdown. If restart is true, there will be a shutdown hook added that will execute + * the {@link #restartCommand} so that new CMR is launched. + * + * @param restart + * If new CMR should be launched. + */ + private void doShutdown(final boolean restart) { + if (restart) { + log.info("Restart initialized"); + } else { + log.info("Shutdown initialized"); + } + + Runnable shutdownRunnable = new Runnable() { + @Override + public void run() { + if (restart) { + if (CMR.isStartedAsService()) { + try { + // Not the best solution. + // Start the Windows console due to the restart of the CMR Windows + // Service through inspectIT UI. + Runtime.getRuntime().exec(RESTART_CMR_COMMAND); + } catch (IOException e) { + log.error(e.getMessage()); + } + } else { + System.exit(RESTART_EXIT_CODE); + } + } else { + if (CMR.isStartedAsService()) { + try { + // Start the Windows console due to the shutdown of the CMR Windows + // Service through inspectIT UI. + Runtime.getRuntime().exec(SHUTDOWN_CMR_COMMAND); + } catch (IOException e) { + log.error(e.getMessage()); + } + } else { + System.exit(0); + } + } + } + }; + // we execute the shutdown in 500ms so that this method can return + executorService.schedule(shutdownRunnable, 500, TimeUnit.MILLISECONDS); + } + + /** + * Loads restart command from startup file. + * + * @throws IOException + * If {@link IOException} occurs during reading. + */ + @PostConstruct + public void postConstruct() throws IOException { + if (log.isInfoEnabled()) { + log.info("|-Shutdown Service active..."); + } + } + +} diff --git a/CMR/src/spring/spring-context-beans.xml b/CMR/src/spring/spring-context-beans.xml new file mode 100644 index 000000000..9213bc555 --- /dev/null +++ b/CMR/src/spring/spring-context-beans.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/spring/spring-context-database.xml b/CMR/src/spring/spring-context-database.xml new file mode 100644 index 000000000..9ba9f48b3 --- /dev/null +++ b/CMR/src/spring/spring-context-database.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + hibernate/PlatformIdent.hbm.xml + hibernate/MethodIdent.hbm.xml + hibernate/SensorTypeIdent.hbm.xml + hibernate/MethodIdentToSensorType.hbm.xml + + + hibernate/DefaultData.hbm.xml + hibernate/ParameterContentData.hbm.xml + hibernate/MethodSensorData.hbm.xml + hibernate/SystemSensorData.hbm.xml + + + hibernate/TimerData.hbm.xml + hibernate/HttpTimerData.hbm.xml + + + hibernate/ClassLoadingInformationData.hbm.xml + hibernate/CompilationInformationData.hbm.xml + hibernate/MemoryInformationData.hbm.xml + hibernate/CpuInformationData.hbm.xml + hibernate/RuntimeInformationData.hbm.xml + hibernate/VMArgumentData.hbm.xml + hibernate/SystemInformationData.hbm.xml + hibernate/ThreadInformationData.hbm.xml + + + hibernate/StorageLabel.hbm.xml + hibernate/StorageLabelType.hbm.xml + + + + + update + ${database.dialect} + ${database.showsql} + ${database.formatsql} + after_transaction + 256 + 50 + true + + + + + diff --git a/CMR/src/spring/spring-context-global.xml b/CMR/src/spring/spring-context-global.xml new file mode 100644 index 000000000..9d319f2b0 --- /dev/null +++ b/CMR/src/spring/spring-context-global.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + diff --git a/CMR/src/spring/spring-context-jetty.xml b/CMR/src/spring/spring-context-jetty.xml new file mode 100644 index 000000000..7c4552262 --- /dev/null +++ b/CMR/src/spring/spring-context-jetty.xml @@ -0,0 +1,327 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CMR/src/spring/spring-context-processors.xml b/CMR/src/spring/spring-context-processors.xml new file mode 100644 index 000000000..01fdb448c --- /dev/null +++ b/CMR/src/spring/spring-context-processors.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + info.novatec.inspectit.communication.data.ClassLoadingInformationData + info.novatec.inspectit.communication.data.CompilationInformationData + info.novatec.inspectit.communication.data.CpuInformationData + info.novatec.inspectit.communication.data.MemoryInformationData + info.novatec.inspectit.communication.data.RuntimeInformationData + info.novatec.inspectit.communication.data.ThreadInformationData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/src/spring/spring-context-rest.xml b/CMR/src/spring/spring-context-rest.xml new file mode 100644 index 000000000..05bbab180 --- /dev/null +++ b/CMR/src/spring/spring-context-rest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CMR/startup.bat b/CMR/startup.bat new file mode 100644 index 000000000..7f9a07366 --- /dev/null +++ b/CMR/startup.bat @@ -0,0 +1,9 @@ +@echo off + +:RESTART +#COMMAND# + +if ERRORLEVEL 10 ( + echo Restarting CMR... + goto RESTART +) \ No newline at end of file diff --git a/CMR/startup.sh b/CMR/startup.sh new file mode 100644 index 000000000..80e8e4655 --- /dev/null +++ b/CMR/startup.sh @@ -0,0 +1,9 @@ +while true; do + #COMMAND# + CMR_STATUS=$? + if [ "$CMR_STATUS" -eq 10 ]; then + echo "Restarting CMR..." + else + exit $CMR_STATUS + fi +done diff --git a/CMR/test/info/novatec/inspectit/cmr/cache/impl/AtomicBufferTest.java b/CMR/test/info/novatec/inspectit/cmr/cache/impl/AtomicBufferTest.java new file mode 100644 index 000000000..1490e247d --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/cache/impl/AtomicBufferTest.java @@ -0,0 +1,437 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.cmr.cache.IBufferElement; +import info.novatec.inspectit.cmr.cache.IObjectSizes; +import info.novatec.inspectit.cmr.test.AbstractTestNGLogSupport; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.indexing.buffer.IBufferTreeComponent; + +import java.util.Random; +import java.util.concurrent.ExecutorService; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Testing of the functionality of the {@link AtomicBuffer}. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("PMD") +public class AtomicBufferTest extends AbstractTestNGLogSupport { + + /** + * Class under test. + */ + private AtomicBuffer buffer; + + @Mock + private BufferProperties bufferProperties; + + @Mock + private IObjectSizes objectSizes; + + @Mock + private IBufferTreeComponent indexingTree; + + /** + * Init. + * + * @throws Exception + */ + @BeforeMethod + public void init() throws Exception { + MockitoAnnotations.initMocks(this); + buffer = new AtomicBuffer<>(); + buffer.bufferProperties = bufferProperties; + buffer.objectSizes = objectSizes; + buffer.indexingTree = indexingTree; + buffer.log = LoggerFactory.getLogger(AtomicBuffer.class); + when(bufferProperties.getIndexingTreeCleaningThreads()).thenReturn(1); + buffer.postConstruct(); + } + + /** + * Test that insertion will be in order. + */ + @Test + public void insertElements() { + DefaultData defaultData = mock(DefaultData.class); + IBufferElement element1 = new BufferElement(defaultData); + IBufferElement element2 = new BufferElement(defaultData); + + buffer.put(element1); + buffer.put(element2); + + assertThat(buffer.getInsertedElemenets(), is(2L)); + assertThat(element1.getNextElement(), is(equalTo(element2))); + } + + /** + * Tests that eviction will remove right amount of elements. + * + * @throws Exception + */ + @Test(invocationCount = 5) + public void eviction() throws Exception { + Random random = new Random(); + long elements = 1 + random.nextInt(10000); + elements += elements % 2; + int analyzers = 1 + random.nextInt(3); + + // evict half of the buffer + when(bufferProperties.getInitialBufferSize()).thenReturn(elements); + when(bufferProperties.getEvictionOccupancyPercentage()).thenReturn(0.1f); + when(bufferProperties.getEvictionFragmentSizePercentage()).thenReturn(0.5f); + buffer.postConstruct(); + + DefaultData defaultData = mock(DefaultData.class); + when(defaultData.getObjectSize(objectSizes)).thenReturn(1L); + + BufferAnalyzer[] analyzerArray = new BufferAnalyzer[analyzers]; + for (int i = 0; i < analyzers; i++) { + BufferAnalyzer bufferAnalyzer = new BufferAnalyzer(buffer); + bufferAnalyzer.start(); + analyzerArray[i] = bufferAnalyzer; + } + + for (int i = 0; i < elements; i++) { + IBufferElement bufferElement = new BufferElement(defaultData); + buffer.put(bufferElement); + } + + // wait to be analyzed + while (buffer.getAnalyzedElements() < elements) { + Thread.sleep(50); + } + + buffer.evict(); + + for (BufferAnalyzer bufferAnalyzer : analyzerArray) { + bufferAnalyzer.interrupt(); + } + + assertThat(buffer.getCurrentSize(), is(elements / 2)); + assertThat(buffer.getInsertedElemenets(), is(elements)); + assertThat(buffer.getEvictedElemenets(), is(elements / 2)); + } + + /** + * Tests that size of the elements is correctly analyzed and added to the buffer size. + * + * @throws Exception + */ + @Test(invocationCount = 5) + public void analysisAndSize() throws Exception { + Random random = new Random(); + // tests needs at least two elements + long elements = 2 + random.nextInt(10000); + elements += elements % 2; + int analyzers = 1 + random.nextInt(3); + + // eviction needed when 99% of the buffer is full + when(bufferProperties.getInitialBufferSize()).thenReturn(elements); + when(bufferProperties.getEvictionOccupancyPercentage()).thenReturn(0.99f); + buffer.postConstruct(); + + DefaultData defaultData = mock(DefaultData.class); + when(defaultData.getObjectSize(objectSizes)).thenReturn(1L); + + // start analyzers + BufferAnalyzer[] analyzerArray = new BufferAnalyzer[analyzers]; + for (int i = 0; i < analyzers; i++) { + BufferAnalyzer bufferAnalyzer = new BufferAnalyzer(buffer); + bufferAnalyzer.start(); + analyzerArray[i] = bufferAnalyzer; + } + + IBufferElement first = null; + long firstRunElements = (long) (elements * 0.99f) - 1; + for (int i = 0; i < firstRunElements; i++) { + IBufferElement bufferElement = new BufferElement(defaultData); + if (0 == i) { + first = bufferElement; + } + buffer.put(bufferElement); + } + + // wait to be analyzed + while (buffer.getAnalyzedElements() < firstRunElements) { + Thread.sleep(50); + } + + assertThat(buffer.getCurrentSize(), is(firstRunElements)); + assertThat(buffer.getOccupancyPercentage(), is((float) firstRunElements / elements)); + assertThat(buffer.shouldEvict(), is(false)); + + // add rest for activating eviction + for (int i = 0; i < elements - firstRunElements; i++) { + IBufferElement bufferElement = new BufferElement(defaultData); + buffer.put(bufferElement); + } + + // wait to be analyzed + while (buffer.getAnalyzedElements() < elements) { + Thread.sleep(50); + } + + // interrupt analyzers + for (BufferAnalyzer bufferAnalyzer : analyzerArray) { + bufferAnalyzer.interrupt(); + } + + assertThat(buffer.getCurrentSize(), is(elements)); + assertThat(buffer.getOccupancyPercentage(), is(1f)); + assertThat(buffer.shouldEvict(), is(true)); + + assertThat(buffer.getAnalyzedElements(), is(elements)); + for (int i = 0; i < elements; i++) { + assertThat(first.isAnalyzed(), is(true)); + first = first.getNextElement(); + } + } + + /** + * Tests that expansion rate will be used on elements size. + * + * @throws Exception + */ + @Test(invocationCount = 5) + public void analysisAndSizeWithExpansionRate() throws Exception { + Random random = new Random(); + long elements = 1 + random.nextInt(10000); + elements += elements % 2; + int analyzers = 1 + random.nextInt(3); + + float expansionRate = 0.1f; + long elementSize = 10; + when(objectSizes.getObjectSecurityExpansionRate()).thenReturn(expansionRate); + buffer.postConstruct(); + + DefaultData defaultData = mock(DefaultData.class); + when(defaultData.getObjectSize(objectSizes)).thenReturn(elementSize); + + // start analyzers + BufferAnalyzer[] analyzerArray = new BufferAnalyzer[analyzers]; + for (int i = 0; i < analyzers; i++) { + BufferAnalyzer bufferAnalyzer = new BufferAnalyzer(buffer); + bufferAnalyzer.start(); + analyzerArray[i] = bufferAnalyzer; + } + + for (int i = 0; i < elements; i++) { + IBufferElement bufferElement = new BufferElement(defaultData); + buffer.put(bufferElement); + } + + // wait to be analyzed + while (buffer.getAnalyzedElements() < elements) { + Thread.sleep(50); + } + + // interrupt analyzers + for (BufferAnalyzer bufferAnalyzer : analyzerArray) { + bufferAnalyzer.interrupt(); + } + + assertThat(buffer.getCurrentSize(), is((long) (elements * elementSize * (1 + expansionRate)))); + } + + /** + * Test that elements are correctly indexed. + * + * @throws Exception + */ + @Test(invocationCount = 5) + public void indexing() throws Exception { + Random random = new Random(); + long elements = 1 + random.nextInt(10000); + elements += elements % 2; + int indexers = 1 + random.nextInt(3); + + when(bufferProperties.getIndexingWaitTime()).thenReturn(10L); + + DefaultData defaultData = mock(DefaultData.class); + when(defaultData.getObjectSize(objectSizes)).thenReturn(1L); + + // start analyzer + BufferAnalyzer bufferAnalyzer = new BufferAnalyzer(buffer); + bufferAnalyzer.start(); + + // start indexers + BufferIndexer[] indexerArray = new BufferIndexer[indexers]; + for (int i = 0; i < indexers; i++) { + BufferIndexer bufferIndexer = new BufferIndexer(buffer); + bufferIndexer.start(); + indexerArray[i] = bufferIndexer; + } + + IBufferElement first = null; + + for (int i = 0; i < elements; i++) { + IBufferElement bufferElement = new BufferElement(defaultData); + if (0 == i) { + first = bufferElement; + } + buffer.put(bufferElement); + } + + // wait for the elements to be analyzed and indexed + while (buffer.getAnalyzedElements() < elements || buffer.getIndexedElements() < elements) { + Thread.sleep(50); + } + + // interrupt workers + bufferAnalyzer.interrupt(); + for (BufferIndexer bufferIndexer : indexerArray) { + bufferIndexer.interrupt(); + } + + for (int i = 0; i < elements; i++) { + assertThat(first.isIndexed(), is(true)); + first = first.getNextElement(); + } + + assertThat(buffer.getIndexedElements(), is(elements)); + verify(indexingTree, times((int) elements)).put(defaultData); + } + + /** + * Tests that the tree size calculations and maintenance is done. + * + * @throws Exception + */ + @Test(invocationCount = 5) + public void indexingTreeMaintenance() throws Exception { + Random random = new Random(); + long flagsSetOnBytes = 30L; + long elements = 1 + random.nextInt(10000); + + // when adding 30 bytes, maintenance should be done + // indexing tree always reports 10 bytes size + when(bufferProperties.getInitialBufferSize()).thenReturn(elements); + when(bufferProperties.getEvictionOccupancyPercentage()).thenReturn(0.5f); + when(bufferProperties.getEvictionFragmentSizePercentage()).thenReturn(0.35f); + when(bufferProperties.getFlagsSetOnBytes(anyLong())).thenReturn(flagsSetOnBytes); + when(bufferProperties.getIndexingWaitTime()).thenReturn(10L); + when(indexingTree.getComponentSize(objectSizes)).thenReturn(10L); + buffer.postConstruct(); + + DefaultData defaultData = mock(DefaultData.class); + when(defaultData.getObjectSize(objectSizes)).thenReturn(1L); + + BufferAnalyzer bufferAnalyzer = new BufferAnalyzer(buffer); + bufferAnalyzer.start(); + + BufferIndexer bufferIndexer = new BufferIndexer(buffer); + bufferIndexer.start(); + + for (int i = 0; i < elements; i++) { + IBufferElement bufferElement = new BufferElement(defaultData); + buffer.put(bufferElement); + } + + // wait for the elements to be analyzed and indexed + while (buffer.getAnalyzedElements() < elements || buffer.getIndexedElements() < elements) { + Thread.sleep(50); + } + + if (elements > flagsSetOnBytes) { + assertThat(buffer.getCurrentSize(), is(elements + 10L)); + verify(indexingTree, atLeast(1)).getComponentSize(objectSizes); + } else { + assertThat(buffer.getCurrentSize(), is(elements)); + verify(indexingTree, times(0)).getComponentSize(objectSizes); + } + + // evict + assertThat(buffer.shouldEvict(), is(true)); + buffer.evict(); + long evicted = buffer.getEvictedElemenets(); + + // now add two more element to active the cleaning of the indexing tree + // the cleaning should be done after adding the first one, so we can execute the verify + // afterwards + buffer.put(new BufferElement(defaultData)); + buffer.put(new BufferElement(defaultData)); + + // wait for the element to be analyzed and indexed + while (buffer.getAnalyzedElements() < elements + 2 || buffer.getIndexedElements() < elements + 2) { + Thread.sleep(50); + } + + bufferAnalyzer.interrupt(); + bufferIndexer.interrupt(); + + // only if we evicted enough we can expect the clean + if (evicted > flagsSetOnBytes) { + verify(indexingTree, times(1)).cleanWithRunnable(Mockito. anyObject()); + } else { + verify(indexingTree, times(0)).cleanWithRunnable(Mockito. anyObject()); + } + } + + /** + * Tests that the clean works properly. + * + * @throws Exception + */ + @Test(invocationCount = 5) + public void clean() throws Exception { + Random random = new Random(); + long elements = 1 + random.nextInt(10000); + + when(bufferProperties.getInitialBufferSize()).thenReturn(elements); + when(bufferProperties.getIndexingWaitTime()).thenReturn(5L); + + DefaultData defaultData = mock(DefaultData.class); + when(defaultData.getObjectSize(objectSizes)).thenReturn(1L); + + BufferAnalyzer bufferAnalyzer = new BufferAnalyzer(buffer); + bufferAnalyzer.start(); + + BufferIndexer bufferIndexer = new BufferIndexer(buffer); + bufferIndexer.start(); + + for (int i = 0; i < elements / 2; i++) { + IBufferElement bufferElement = new BufferElement(defaultData); + buffer.put(bufferElement); + buffer.put(bufferElement); + + // execute clear all the time, let s push this all threads + buffer.clearAll(); + } + + for (int i = 0; i < elements; i++) { + IBufferElement bufferElement = new BufferElement(defaultData); + buffer.put(bufferElement); + } + + // wait for the elements to be analyzed and indexed + while (buffer.getAnalyzedElements() < elements || buffer.getIndexedElements() < elements) { + Thread.sleep(500); + } + + bufferAnalyzer.interrupt(); + bufferIndexer.interrupt(); + + assertThat(buffer.getCurrentSize(), is(elements)); + assertThat(buffer.getAnalyzedElements(), is(elements)); + assertThat(buffer.getIndexedElements(), is(elements)); + assertThat(buffer.getEvictedElemenets(), is(0L)); + } +} diff --git a/CMR/test/info/novatec/inspectit/cmr/cache/impl/BufferPropertiesTest.java b/CMR/test/info/novatec/inspectit/cmr/cache/impl/BufferPropertiesTest.java new file mode 100644 index 000000000..01d5f8c5c --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/cache/impl/BufferPropertiesTest.java @@ -0,0 +1,173 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.lessThan; +import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.mockito.Mockito.mock; +import info.novatec.inspectit.cmr.test.AbstractTestNGLogSupport; + +import org.slf4j.Logger; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Testing the calculations inside of {@link BufferProperties} class. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("PMD") +public class BufferPropertiesTest extends AbstractTestNGLogSupport { + + /** + * Buffer properties to test. + */ + private BufferProperties bufferProperties; + + /** + * Initialization of the buffer properties. + * + * @throws Exception + * if an exception is thrown while executing the post construct method in the buffer + * properties. + */ + @BeforeClass + public void init() throws Exception { + bufferProperties = new BufferProperties(); + bufferProperties.log = mock(Logger.class); + bufferProperties.bytesMaintenancePercentage = 0.2f; + bufferProperties.evictionFragmentSizePercentage = 0.1f; + bufferProperties.evictionOccupancyPercentage = 0.8f; + bufferProperties.indexingTreeCleaningThreads = 1; + bufferProperties.indexingWaitTime = 500l; + bufferProperties.maxObjectExpansionRate = 0.05f; + bufferProperties.maxObjectExpansionRateActiveTillBufferSize = 10; + bufferProperties.maxObjectExpansionRateActiveFromOccupancy = 0.75f; + bufferProperties.maxOldSpaceOccupancy = 0.9f; + bufferProperties.maxOldSpaceOccupancyActiveFromOldGenSize = 1024 * 1024 * 100; + bufferProperties.minObjectExpansionRate = 0.02f; + bufferProperties.minObjectExpansionRateActiveFromBufferSize = 100; + bufferProperties.minObjectExpansionRateActiveTillOccupancy = 0.35f; + bufferProperties.minOldSpaceOccupancy = 0.2f; + bufferProperties.minOldSpaceOccupancyActiveTillOldGenSize = 1024 * 1024 * 200; + bufferProperties.postConstruct(); + } + + /** + * General Parameterized test to assure that no matter how big buffer size is, expansion rate + * will be between min and max related. + * + * @param bufferSize + * Buffer size. + */ + @Test(dataProvider = "Buffer-Size-Provider") + public void parametrizedExpansionRateTest(long bufferSize) { + float expansionRate = bufferProperties.getObjectSecurityExpansionRate(bufferSize); + assertThat(expansionRate, is(lessThanOrEqualTo(bufferProperties.getMaxObjectExpansionRate()))); + assertThat(expansionRate, is(greaterThanOrEqualTo(bufferProperties.getMinObjectExpansionRate()))); + + float relatedToSize = bufferProperties.getObjectSecurityExpansionRateBufferSize(bufferSize); + float relatedToOccupancy = bufferProperties.getObjectSecurityExpansionRateBufferOccupancy(bufferSize, bufferProperties.getOldGenMax()); + assertThat(expansionRate, is(equalTo((relatedToSize + relatedToOccupancy) / 2))); + } + + /** + * Parameterized test to assure that no matter how big buffer size is, expansion rate will be + * between min and max related to buffers size. + * + * @param bufferSize + * Buffer size. + */ + @Test(dataProvider = "Buffer-Size-Provider") + public void parametrizedExpansionRateTestBufferSize(long bufferSize) { + float expansionRate = bufferProperties.getObjectSecurityExpansionRateBufferSize(bufferSize); + assertThat(expansionRate, is(lessThanOrEqualTo(bufferProperties.getMaxObjectExpansionRate()))); + assertThat(expansionRate, is(greaterThanOrEqualTo(bufferProperties.getMinObjectExpansionRate()))); + + if (bufferSize > bufferProperties.getMaxObjectExpansionRateActiveTillBufferSize() && bufferSize < bufferProperties.getMinObjectExpansionRateActiveFromBufferSize()) { + assertThat(expansionRate, is(lessThan(bufferProperties.getMaxObjectExpansionRate()))); + assertThat(expansionRate, is(greaterThan(bufferProperties.getMinObjectExpansionRate()))); + } + } + + /** + * Parameterized test to assure that no matter how big buffer size is, expansion rate will be + * between min and max related to buffer occupancy. + * + * @param bufferSize + * Buffer size. + */ + @Test(dataProvider = "Buffer-Size-Provider") + public void parametrizedExpansionRateTestBufferOccupancy(long bufferSize) { + float expansionRate = bufferProperties.getObjectSecurityExpansionRateBufferOccupancy(bufferSize, bufferProperties.getOldGenMax()); + assertThat(expansionRate, is(lessThanOrEqualTo(bufferProperties.getMaxObjectExpansionRate()))); + assertThat(expansionRate, is(greaterThanOrEqualTo(bufferProperties.getMinObjectExpansionRate()))); + + long maxOldGen = bufferProperties.getOldGenMax(); + float occupancy = bufferSize / maxOldGen; + + if (occupancy > bufferProperties.getMinObjectExpansionRateActiveTillOccupancy() && occupancy < bufferProperties.getMaxObjectExpansionRateActiveFromOccupancy()) { + assertThat(expansionRate, is(lessThan(bufferProperties.getMaxObjectExpansionRate()))); + assertThat(expansionRate, is(greaterThan(bufferProperties.getMinObjectExpansionRate()))); + } + } + + /** + * Single expansion rate test for buffer size. + */ + @Test + public void singleExpansionRateTestBufferSize() { + long bufferSize = bufferProperties.getMaxObjectExpansionRateActiveTillBufferSize() + + (bufferProperties.getMinObjectExpansionRateActiveFromBufferSize() - bufferProperties.getMaxObjectExpansionRateActiveTillBufferSize()) / 2; + float expansionRate = bufferProperties.getObjectSecurityExpansionRateBufferSize(bufferSize); + float expectedRate = bufferProperties.getMinObjectExpansionRate() + (bufferProperties.getMaxObjectExpansionRate() - bufferProperties.getMinObjectExpansionRate()) / 2; + assertThat(expansionRate, is(equalTo(expectedRate))); + } + + /** + * Single expansion rate test for buffer occupancy. + */ + @Test + public void singleExpansionRateTestBufferOccupancy() { + long oldGenMax = 100; + long bufferSize = (long) (oldGenMax * (bufferProperties.maxObjectExpansionRateActiveFromOccupancy - (bufferProperties.getMaxObjectExpansionRateActiveFromOccupancy() - bufferProperties + .getMinObjectExpansionRateActiveTillOccupancy()) / 2)); + + float expansionRate = bufferProperties.getObjectSecurityExpansionRateBufferOccupancy(bufferSize, oldGenMax); + float expectedRate = bufferProperties.getMinObjectExpansionRate() + (bufferProperties.getMaxObjectExpansionRate() - bufferProperties.getMinObjectExpansionRate()) / 2; + assertThat(expansionRate, is(equalTo(expectedRate))); + } + + /** + * Parameters generation for {@link #parametrizedExpansionRateTest(long)}. + * + * @return Buffer size. + */ + @DataProvider(name = "Buffer-Size-Provider") + public Object[][] bufferSizeParameterProvider() { + int size = 50; + Object[][] parameters = new Object[size][1]; + for (int i = 0; i < size; i++) { + parameters[i][0] = getRandomLong(2000000000L); + } + return parameters; + } + + /** + * Returns random positive long number smaller than given max value. + * + * @param max + * Max value. + * @return Long. + */ + private long getRandomLong(long max) { + long value = (long) (Math.random() * max); + return value - value % 10 + 10; + } + +} diff --git a/CMR/test/info/novatec/inspectit/cmr/cache/impl/MemoryCalculationTest.java b/CMR/test/info/novatec/inspectit/cmr/cache/impl/MemoryCalculationTest.java new file mode 100644 index 000000000..2a959dc72 --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/cache/impl/MemoryCalculationTest.java @@ -0,0 +1,659 @@ +package info.novatec.inspectit.cmr.cache.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.is; +import info.novatec.inspectit.cmr.cache.AbstractObjectSizes; +import info.novatec.inspectit.cmr.cache.IObjectSizes; +import info.novatec.inspectit.cmr.test.AbstractTestNGLogSupport; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.communication.Sizeable; +import info.novatec.inspectit.communication.data.AggregatedExceptionSensorData; +import info.novatec.inspectit.communication.data.AggregatedHttpTimerData; +import info.novatec.inspectit.communication.data.AggregatedSqlStatementData; +import info.novatec.inspectit.communication.data.AggregatedTimerData; +import info.novatec.inspectit.communication.data.ClassLoadingInformationData; +import info.novatec.inspectit.communication.data.CompilationInformationData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.InvocationAwareData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.MemoryInformationData; +import info.novatec.inspectit.communication.data.RuntimeInformationData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.SystemInformationData; +import info.novatec.inspectit.communication.data.ThreadInformationData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.util.UnderlyingSystemInfo; +import info.novatec.inspectit.util.UnderlyingSystemInfo.JvmProvider; + +import java.lang.reflect.Field; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.Stack; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.lang.RandomStringUtils; +import org.apache.log4j.Logger; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import com.javamex.classmexer.MemoryUtil; +import com.javamex.classmexer.MemoryUtil.VisibilityFilter; + +/** + * This test class provides test for the method of {@link IObjectSizes} that calculate the size of + * the core Java classes. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("PMD") +public class MemoryCalculationTest extends AbstractTestNGLogSupport { + + /** + * The logger of this class. + */ + private static final Logger LOGGER = Logger.getLogger(MemoryCalculationTest.class); + + /** + * Our classes to be tested. + */ + public static final Object[][] TESTING_CLASSES = new Object[][] { { TestDefaultData.class }, { TestMethodSensorData.class }, { TestInvocationAwareData.class }, { TimerData.class }, + { SqlStatementData.class }, { ExceptionSensorData.class }, { InvocationSequenceData.class }, { ClassLoadingInformationData.class }, { CompilationInformationData.class }, + { MemoryInformationData.class }, { RuntimeInformationData.class }, { SystemInformationData.class }, { ThreadInformationData.class }, { HttpTimerData.class }, + { AggregatedExceptionSensorData.class }, { AggregatedHttpTimerData.class }, { AggregatedSqlStatementData.class }, { AggregatedTimerData.class } }; + + /** + * Amount that we add to each hash map because of the entry, key and value set safety. + */ + private static long HASH_MAP_SAFTY_DELTA; + + /** + * Field for knowing how much big is the hash map table. + */ + private static Field oracleHashMapTable; + + /** + * Field for knowing how much big is the hash map table. + */ + private static Field ibmHashMapTable; + + /** + * {@link IObjectSizes}. + */ + private IObjectSizes objectSizes; + + /** + * Gets the proper instance of the {@link IObjectSizes} because it s dependent on the system + * test is run on. + * + * @throws Exception + * If Exception occurs. + */ + @BeforeClass + public void initCorrectObjectSizesInstance() throws Exception { + objectSizes = new ObjectSizesFactory().getObject(); + + if (UnderlyingSystemInfo.JVM_PROVIDER == JvmProvider.SUN || UnderlyingSystemInfo.JVM_PROVIDER == JvmProvider.ORACLE) { + oracleHashMapTable = HashMap.class.getDeclaredField("table"); // NOPMD + if (null != oracleHashMapTable) { + oracleHashMapTable.setAccessible(true); + } + } else if (UnderlyingSystemInfo.JVM_PROVIDER == JvmProvider.IBM) { + ibmHashMapTable = HashMap.class.getDeclaredField("table"); // NOPMD + if (null != ibmHashMapTable) { + ibmHashMapTable.setAccessible(true); + } + } + + HASH_MAP_SAFTY_DELTA = objectSizes.getSizeOfHashMapKeyEntrySet(); + } + + /** + * Tests size of a simple {@link Object}. + */ + @Test + public void object() { + Object o = new Object(); + long ourSize = objectSizes.getSizeOfObjectObject(); + long theirSize = MemoryUtil.deepMemoryUsageOf(o, VisibilityFilter.ALL); + assertThat(ourSize, is(equalTo(theirSize))); + } + + /** + * Tests size of a {@link Long}. + */ + @Test + public void longObject() { + Long l = Long.valueOf(1); + long ourSize = objectSizes.getSizeOfLongObject(); + long theirSize = MemoryUtil.deepMemoryUsageOf(l, VisibilityFilter.ALL); + assertThat(ourSize, is(equalTo(theirSize))); + } + + /** + * Tests size of a {@link Integer}. + */ + @Test + public void integerObject() { + Integer i = Integer.valueOf(1); + long ourSize = objectSizes.getSizeOfIntegerObject(); + long theirSize = MemoryUtil.deepMemoryUsageOf(i, VisibilityFilter.ALL); + assertThat(ourSize, is(equalTo(theirSize))); + } + + /** + * Tests size of a {@link Short}. + */ + @Test + public void shortObject() { + Short s = Short.valueOf((short) 1); // NOPMD + long ourSize = objectSizes.getSizeOfShortObject(); + long theirSize = MemoryUtil.deepMemoryUsageOf(s, VisibilityFilter.ALL); + assertThat(ourSize, is(equalTo(theirSize))); + } + + /** + * Tests size of a {@link Character}. + */ + @Test + public void characterObject() { + Character s = Character.valueOf('c'); + long ourSize = objectSizes.getSizeOfCharacterObject(); + long theirSize = MemoryUtil.deepMemoryUsageOf(s, VisibilityFilter.ALL); + assertThat(ourSize, is(equalTo(theirSize))); + } + + /** + * Tests size of a {@link Boolean}. + */ + @Test + public void booleanObject() { + Boolean b = Boolean.TRUE; + long ourSize = objectSizes.getSizeOfBooleanObject(); + long theirSize = MemoryUtil.deepMemoryUsageOf(b, VisibilityFilter.ALL); + assertThat(ourSize, is(equalTo(theirSize))); + } + + /** + * Tests empty{@link String}. + */ + @Test(invocationCount = 50) + public void empryString() { + String s = ""; + long ourSize = objectSizes.getSizeOf(s); + long theirSize = MemoryUtil.deepMemoryUsageOf(s, VisibilityFilter.ALL); + assertThat(ourSize, is(equalTo(theirSize))); + } + + /** + * Tests size of a random {@link String} with length from 1 - 100 characters. + */ + @Test(invocationCount = 50) + public void string() { + int size = (int) (Math.random() * 100); + String s = RandomStringUtils.random(size); + long ourSize = objectSizes.getSizeOf(s); + long theirSize = MemoryUtil.deepMemoryUsageOf(s, VisibilityFilter.ALL); + assertThat(ourSize, is(equalTo(theirSize))); + } + + /** + * Tests size of a {@link Timestamp}. + */ + @Test + public void timestamp() { + Timestamp t = new Timestamp(new Date().getTime()); + long ourSize = objectSizes.getSizeOf(t); + long theirSize = MemoryUtil.deepMemoryUsageOf(t, VisibilityFilter.ALL); + assertThat(ourSize, is(equalTo(theirSize))); + } + + /** + * Test the array of size 0 and 1. + */ + @Test + public void emptyArray() { + Object[] array = new Object[0]; + long ourSize = objectSizes.getSizeOfArray(array.length); + long theirSize = MemoryUtil.deepMemoryUsageOf(array, VisibilityFilter.ALL); + assertThat(ourSize, is(equalTo(theirSize))); + + array = new Object[1]; + ourSize = objectSizes.getSizeOfArray(array.length); + theirSize = MemoryUtil.deepMemoryUsageOf(array, VisibilityFilter.ALL); + assertThat(ourSize, is(equalTo(theirSize))); + } + + /** + * General test for arrays of different sizes. + */ + @Test(invocationCount = 50) + public void array() { + Object[] array = new Object[(int) (Math.random() * 100)]; + long ourSize = objectSizes.getSizeOfArray(array.length); + long theirSize = MemoryUtil.deepMemoryUsageOf(array, VisibilityFilter.ALL); + assertThat("Empty array", ourSize, is(equalTo(theirSize))); + + for (int i = 0; i < array.length; i++) { + array[i] = new Object(); + } + + if (checkObjectGraphIdentityHashCodeCollision(array, new HashSet(), new ArrayList())) { + LOGGER.info("Object graph identity collision check passed for test: array()"); + ourSize = objectSizes.getSizeOfArray(array.length) + array.length * objectSizes.getSizeOfObjectObject(); + theirSize = MemoryUtil.deepMemoryUsageOf(array, VisibilityFilter.ALL); + assertThat("Full array of " + array.length + " elements", ourSize, is(equalTo(theirSize))); + } + } + + /** + * boolean array. + */ + @Test(invocationCount = 10) + public void booleanArray() { + boolean[] array = new boolean[(int) (Math.random() * 100)]; + long ourSize = objectSizes.getSizeOfPrimitiveArray(array.length, AbstractObjectSizes.BOOLEAN_SIZE); + long theirSize = MemoryUtil.deepMemoryUsageOf(array, VisibilityFilter.ALL); + assertThat("boolean array", ourSize, is(equalTo(theirSize))); + } + + /** + * char array. + */ + @Test(invocationCount = 10) + public void charArray() { + char[] array = new char[(int) (Math.random() * 100)]; + long ourSize = objectSizes.getSizeOfPrimitiveArray(array.length, AbstractObjectSizes.CHAR_SIZE); + long theirSize = MemoryUtil.deepMemoryUsageOf(array, VisibilityFilter.ALL); + assertThat("char array", ourSize, is(equalTo(theirSize))); + } + + /** + * int array. + */ + @Test(invocationCount = 10) + public void intArray() { + int[] array = new int[(int) (Math.random() * 100)]; + long ourSize = objectSizes.getSizeOfPrimitiveArray(array.length, AbstractObjectSizes.INT_SIZE); + long theirSize = MemoryUtil.deepMemoryUsageOf(array, VisibilityFilter.ALL); + assertThat("int array", ourSize, is(equalTo(theirSize))); + } + + /** + * float array. + */ + @Test(invocationCount = 10) + public void floatArray() { + float[] array = new float[(int) (Math.random() * 100)]; + long ourSize = objectSizes.getSizeOfPrimitiveArray(array.length, AbstractObjectSizes.FLOAT_SIZE); + long theirSize = MemoryUtil.deepMemoryUsageOf(array, VisibilityFilter.ALL); + assertThat("float array", ourSize, is(equalTo(theirSize))); + } + + /** + * long array. + */ + @Test(invocationCount = 10) + public void longArray() { + long[] array = new long[(int) (Math.random() * 100)]; + long ourSize = objectSizes.getSizeOfPrimitiveArray(array.length, AbstractObjectSizes.LONG_SIZE); + long theirSize = MemoryUtil.deepMemoryUsageOf(array, VisibilityFilter.ALL); + assertThat("long array", ourSize, is(equalTo(theirSize))); + } + + /** + * double array. + */ + @Test(invocationCount = 10) + public void doubleArray() { + double[] array = new double[(int) (Math.random() * 100)]; + long ourSize = objectSizes.getSizeOfPrimitiveArray(array.length, AbstractObjectSizes.DOUBLE_SIZE); + long theirSize = MemoryUtil.deepMemoryUsageOf(array, VisibilityFilter.ALL); + assertThat("double array", ourSize, is(equalTo(theirSize))); + } + + /** + * short array. + */ + @Test(invocationCount = 10) + public void shortArray() { + short[] array = new short[(int) (Math.random() * 100)]; + long ourSize = objectSizes.getSizeOfPrimitiveArray(array.length, AbstractObjectSizes.SHORT_SIZE); + long theirSize = MemoryUtil.deepMemoryUsageOf(array, VisibilityFilter.ALL); + assertThat("short array", ourSize, is(equalTo(theirSize))); + } + + + /** + * Tests size of empty and populated {@link ArrayList} object. + */ + @Test(invocationCount = 50) + public void arrayList() { + ArrayList arrayList = new ArrayList(); + long ourSize = objectSizes.getSizeOf(arrayList); + long theirSize = MemoryUtil.deepMemoryUsageOf(arrayList, VisibilityFilter.ALL); + assertThat("Empty list", ourSize, is(equalTo(theirSize))); + + int size = (int) (Math.random() * 100); + for (int i = 0; i < size; i++) { + arrayList.add(new Object()); + } + + if (checkObjectGraphIdentityHashCodeCollision(arrayList, new HashSet(), new ArrayList())) { + LOGGER.info("Object graph identity collision check passed for test: arrayList()"); + ourSize = objectSizes.getSizeOf(arrayList) + size * objectSizes.getSizeOfObjectObject(); + theirSize = MemoryUtil.deepMemoryUsageOf(arrayList, VisibilityFilter.ALL); + assertThat("Random list size", ourSize, is(equalTo(theirSize))); + } + } + + /** + * Tests size of empty and populated {@link ArrayList} object with initial size of 0. + */ + @Test(invocationCount = 50) + public void arrayListInitializeZero() { + ArrayList arrayList = new ArrayList(0); + long ourSize = objectSizes.getSizeOf(arrayList, 0); + long theirSize = MemoryUtil.deepMemoryUsageOf(arrayList, VisibilityFilter.ALL); + assertThat("Empty list", ourSize, is(equalTo(theirSize))); + + int size = (int) (Math.random() * 100); + for (int i = 0; i < size; i++) { + arrayList.add(new Object()); + } + + if (checkObjectGraphIdentityHashCodeCollision(arrayList, new HashSet(), new ArrayList())) { + LOGGER.info("Object graph identity collision check passed for test: arrayListInitializeZero()"); + ourSize = objectSizes.getSizeOf(arrayList, 0) + size * objectSizes.getSizeOfObjectObject(); + theirSize = MemoryUtil.deepMemoryUsageOf(arrayList, VisibilityFilter.ALL); + assertThat("Random list size", ourSize, is(equalTo(theirSize))); + } + } + + /** + * Tests size of empty and populated {@link HashMap} object. + * + * @throws IllegalAccessException + * @throws IllegalArgumentException + */ + @Test(invocationCount = 50) + public void hashMap() throws IllegalArgumentException, IllegalAccessException { + HashMap hashMap = new HashMap(); + long ourSize = objectSizes.getSizeOfHashMap(0); + long theirSize = MemoryUtil.deepMemoryUsageOf(hashMap, VisibilityFilter.ALL); + assertThat("Empty map", ourSize, is(equalTo(theirSize + HASH_MAP_SAFTY_DELTA))); + + int size = (int) (Math.random() * 100); + for (int i = 0; i < size; i++) { + hashMap.put(new Object(), new Object()); + } + + if (checkObjectGraphIdentityHashCodeCollision(hashMap, new HashSet(), new ArrayList())) { + LOGGER.info("Object graph identity collision check passed for test: hashMap()"); + ourSize = objectSizes.getSizeOfHashMap(hashMap.size()) + 2 * size * objectSizes.getSizeOfObjectObject(); + theirSize = MemoryUtil.deepMemoryUsageOf(hashMap, VisibilityFilter.ALL); + + boolean tableCorrect = false; + Object[] table = null; + + if (null != oracleHashMapTable) { + table = (Object[]) oracleHashMapTable.get(hashMap); + } else if (null != ibmHashMapTable) { + table = (Object[]) ibmHashMapTable.get(hashMap); + } + if (null != table) { + int tableSize = table.length; + int ourTableSize = ((AbstractObjectSizes) objectSizes).getHashMapCapacityFromSize(size, 16); + tableCorrect = tableSize == ourTableSize; + } + + if (tableCorrect) { + assertThat("Random map size of " + size, ourSize, is(equalTo(theirSize + HASH_MAP_SAFTY_DELTA))); + } else { + assertThat("Random map size of " + size, ourSize, is(greaterThanOrEqualTo(theirSize + HASH_MAP_SAFTY_DELTA))); + } + } + } + + /** + * Tests size of empty and populated {@link HashSet} object. + */ + @Test + public void hashSet() { + HashSet hashSet = new HashSet(); + long ourSize = objectSizes.getSizeOfHashSet(hashSet.size()); + long theirSize = MemoryUtil.deepMemoryUsageOf(hashSet, VisibilityFilter.ALL); + assertThat("Empty set", ourSize, is(equalTo(theirSize + HASH_MAP_SAFTY_DELTA))); + // no hashSet can pass checkObjectGraphIdentityHashCodeCollision check, so we don't checks + // with elements + // since it is anyway holding just the HashMap, testing HashMap will be enough + } + + /** + * Tests size of empty and populated {@link ConcurrentHashMap} object. Note that populated + * {@link ConcurrentHashMap} is being tested with only 1 segment. It is impossible to provide + * the correct size with more segments, because it is unknown what will be the distributon of + * elements between segments. + */ + @Test(invocationCount = 50) + public void concurrentHashMap() { + // we can only precisely calculate random amount of elements with one segment + int concurrencyLevel = 1; + ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap(16, 0.75f, concurrencyLevel); + long theirSize = MemoryUtil.deepMemoryUsageOf(concurrentHashMap, VisibilityFilter.ALL); + long ourSize = objectSizes.getSizeOfConcurrentHashMap(0, concurrencyLevel); + assertThat("Empty map", ourSize, is(equalTo(theirSize))); + + concurrentHashMap = new ConcurrentHashMap(16, 0.75f, 1); + int size = (int) (Math.random() * 100); + for (int i = 0; i < size; i++) { + concurrentHashMap.put(new Object(), new Object()); + } + + if (checkObjectGraphIdentityHashCodeCollision(concurrentHashMap, new HashSet(), new ArrayList())) { + LOGGER.info("Object graph identity collision check passed for test: concurrentHashMap()"); + theirSize = MemoryUtil.deepMemoryUsageOf(concurrentHashMap, VisibilityFilter.ALL); + ourSize = objectSizes.getSizeOfConcurrentHashMap(size, 1) + size * 2 * objectSizes.getSizeOfObjectObject(); + assertThat("Random map size of " + size, ourSize, is(equalTo(theirSize))); + } + } + + /** + * Tests the arbitrary class created for test purposes. + */ + @Test + public void arbitraryClass() { + TestClass testObject = new TestClass(); + long ourSize = objectSizes.getSizeOf(testObject); + long theirSize = MemoryUtil.deepMemoryUsageOf(testObject, VisibilityFilter.ALL); + assertThat("Test class", ourSize, is(equalTo(theirSize))); + } + + /** + * Tests the special boolean test class to assuer boolean sizes in JVM are in fact compiled as + * int. + */ + @Test + public void booleanTestClass() { + BooleanTestClass testObject = new BooleanTestClass(); + long ourSize = objectSizes.getSizeOf(testObject); + long theirSize = MemoryUtil.deepMemoryUsageOf(testObject, VisibilityFilter.ALL); + assertThat("Boolean test class", ourSize, is(equalTo(theirSize))); + } + + /** + * Tests the inspectIT classes instances created by reflection. + *

+ * Important: The {@link SqlStatementData} class has a problem with calculation when Sun + * JVM is used. The 8 bytes are calculated more than needed. Thus, we need to have the "closeTo" + * assertion until this problem is fixed. This problem is now part of the INSPECTIT-705 ticket. + * + * @param defaultDataClass + * Class to test. + * @throws InstantiationException + * If {@link InstantiationException} occurs. + * @throws IllegalAccessException + * If {@link IllegalAccessException} occurs. + */ + @Test(dataProvider = "classProvider") + public void inspectitClasses(Class defaultDataClass) throws InstantiationException, IllegalAccessException { + DefaultData object = defaultDataClass.newInstance(); + long ourSize = objectSizes.getSizeOf(object); + long theirSize = MemoryUtil.deepMemoryUsageOf(object, VisibilityFilter.ALL); + assertThat("Size of " + defaultDataClass.getName(), ourSize, is(theirSize)); + LOGGER.info("Tested class " + defaultDataClass.getName() + " -> our size: " + ourSize + ", their size: " + theirSize); + } + + /** + * Checks for the possible object graph identity hash code collision. Objects that don't pass + * this check can not be given to the classmexer for size calculations, because correct size + * will not be calculated due to the collision that will occur in the classmexer internally. + *

+ * Most of this method is copied by the classmexer. + * + * @param obj + * Object to check. Note that this method is recursive. + * @param counted + * HashSet that will serve as the collision tester + * @param countedList + * List that will hold all the original identity values. + * + * @return True if there is no collisions in the hash set (map) and object can be tested via + * classmexer. False otherwise. + * @throws SecurityException + */ + private static boolean checkObjectGraphIdentityHashCodeCollision(Object obj, Set counted, List countedList) throws SecurityException { + Stack stack = new Stack(); + stack.push(obj); + while (!(stack.isEmpty())) { + Object object = stack.pop(); + Integer identityHashCode = Integer.valueOf(System.identityHashCode(object)); + boolean added = counted.add(identityHashCode); + if (!added) { + if (!countedList.contains(identityHashCode)) { + return false; + } + } else { + Class clazz = object.getClass(); + Class compType = clazz.getComponentType(); + if ((compType != null) && (!(compType.isPrimitive()))) { + Object[] array = (Object[]) object; + for (int i = 0; i < array.length; i++) { + Object element = array[i]; + if (element != null) { + stack.push(element); + } + } + } + while (clazz != null) { + for (Field field : clazz.getDeclaredFields()) { + int mod = field.getModifiers(); + if ((mod & 0x8) == 0) { + Class fieldClass = field.getType(); + if (!(fieldClass.isPrimitive())) { + if (!(field.isAccessible())) { + field.setAccessible(true); + } + try { + Object subObject = field.get(object); + if (subObject != null) { + stack.push(subObject); + } + } catch (IllegalAccessException illAcc) { + throw new InternalError("Couldn't read " + field); + } + } + } + } + clazz = clazz.getSuperclass(); + } + } + } + return true; + } + + /** + * Provides classes to be tested. + * + * @return Provides classes to be tested. + */ + @DataProvider(name = "classProvider") + public Object[][] classprovider() { + return TESTING_CLASSES; // NOPMD + } + + @SuppressWarnings("unused") + private static class TestClass implements Sizeable { + + private boolean booleanField; + + private int intField; + + private long longField; + + private String str = RandomStringUtils.random((int) (Math.random() * 100)); + + @Override + public long getObjectSize(IObjectSizes objectSizes) { + long size = objectSizes.getSizeOfObjectHeader(); + size += objectSizes.getPrimitiveTypesSize(1, 1, 1, 0, 1, 0); + size += objectSizes.getSizeOf(str); + return objectSizes.alignTo8Bytes(size); + } + + /** + * {@inheritDoc} + */ + public long getObjectSize(IObjectSizes objectSizes, boolean doAlign) { + return getObjectSize(objectSizes); + } + } + + @SuppressWarnings("unused") + private static class BooleanTestClass implements Sizeable { + + private boolean b1, b2, b3, b4, b5, b6, b7, b8, b9; + + @Override + public long getObjectSize(IObjectSizes objectSizes) { + long size = objectSizes.getSizeOfObjectHeader(); + size += objectSizes.getPrimitiveTypesSize(0, 9, 0, 0, 0, 0); + return objectSizes.alignTo8Bytes(size); + } + + /** + * {@inheritDoc} + */ + public long getObjectSize(IObjectSizes objectSizes, boolean doAlign) { + return getObjectSize(objectSizes); + } + } + + @SuppressWarnings("serial") + public static class TestDefaultData extends DefaultData { + }; + + @SuppressWarnings("serial") + public static class TestMethodSensorData extends MethodSensorData { + }; + + @SuppressWarnings("serial") + public static class TestInvocationAwareData extends InvocationAwareData { + + @Override + public double getInvocationAffiliationPercentage() { + return 0; + } + }; +} diff --git a/CMR/test/info/novatec/inspectit/cmr/dao/impl/PlatformIdentDaoTest.java b/CMR/test/info/novatec/inspectit/cmr/dao/impl/PlatformIdentDaoTest.java new file mode 100644 index 000000000..1ee5bc44c --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/dao/impl/PlatformIdentDaoTest.java @@ -0,0 +1,40 @@ +package info.novatec.inspectit.cmr.dao.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import info.novatec.inspectit.cmr.dao.PlatformIdentDao; +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.cmr.test.AbstractTransactionalTestNGLogSupport; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.testng.annotations.Test; + +@ContextConfiguration(locations = { "classpath:spring/spring-context-global.xml", "classpath:spring/spring-context-database.xml", "classpath:spring/spring-context-beans.xml", + "classpath:spring/spring-context-processors.xml", "classpath:spring/spring-context-storage-test.xml" }) +public class PlatformIdentDaoTest extends AbstractTransactionalTestNGLogSupport { + + @Autowired + PlatformIdentDao platformIdentDao; + + /** + * Tests that the saving and deleting the {@link PlatformIdent} works. + */ + @Test + public void deletePlatformIdent() { + PlatformIdent platformIdent = new PlatformIdent(); + platformIdent.setAgentName("TestPlatform"); + + platformIdentDao.saveOrUpdate(platformIdent); + Long id = platformIdent.getId(); + + assertThat(platformIdent.getId(), is(greaterThan(0L))); + + platformIdentDao.delete(platformIdent); + + assertThat(platformIdentDao.load(id), is(nullValue())); + } + +} diff --git a/CMR/test/info/novatec/inspectit/cmr/dao/impl/TimerDataAggregatorTest.java b/CMR/test/info/novatec/inspectit/cmr/dao/impl/TimerDataAggregatorTest.java new file mode 100644 index 000000000..d0d2fa423 --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/dao/impl/TimerDataAggregatorTest.java @@ -0,0 +1,248 @@ +package info.novatec.inspectit.cmr.dao.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.anyDouble; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.cmr.test.AbstractTestNGLogSupport; +import info.novatec.inspectit.communication.data.DatabaseAggregatedTimerData; +import info.novatec.inspectit.communication.data.ParameterContentData; +import info.novatec.inspectit.communication.data.TimerData; + +import java.sql.Timestamp; +import java.util.Date; +import java.util.Random; +import java.util.Set; + +import org.hibernate.SessionFactory; +import org.hibernate.StatelessSession; +import org.hibernate.Transaction; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Test of {@link TimerDataAggregator}. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("PMD") +public class TimerDataAggregatorTest extends AbstractTestNGLogSupport { + + /** + * {@link TimerDataAggregator} to test. + */ + private TimerDataAggregator aggregator; + + private StatelessSession session; + + /** + * Initialize. + */ + @BeforeMethod + public void init() { + SessionFactory factory = mock(SessionFactory.class); + session = mock(StatelessSession.class); + Transaction transaction = mock(Transaction.class); + when(factory.openStatelessSession()).thenReturn(session); + when(session.beginTransaction()).thenReturn(transaction); + + aggregator = new TimerDataAggregator(factory); + aggregator.aggregationPeriod = 5l; + aggregator.cacheCleanSleepingPeriod = 10; + aggregator.maxElements = 100; + } + + /** + * Tests that after maximum amount of elements is reached we move them to persist list. + */ + @Test + public void maxElementsReached() { + aggregator.maxElements = 1; + + TimerData timerData1 = new TimerData(new Timestamp(System.currentTimeMillis()), 10L, 20L, 30L); + TimerData timerData2 = new TimerData(new Timestamp(System.currentTimeMillis()), 100L, 200L, 300L); + + aggregator.processTimerData(timerData1); + aggregator.processTimerData(timerData2); + + assertThat(aggregator.getElementCount(), is(1)); + verifyZeroInteractions(session); + } + + /** + * Tests that if we place many time same amount of elements, maximum will not be reached. + */ + @Test + public void noMaxElementsReached() { + aggregator.maxElements = 2; + + TimerData timerData1 = new TimerData(new Timestamp(System.currentTimeMillis()), 10L, 20L, 30L); + TimerData timerData2 = new TimerData(new Timestamp(System.currentTimeMillis()), 100L, 200L, 300L); + + for (int i = 0; i < 100; i++) { + aggregator.processTimerData(timerData1); + aggregator.processTimerData(timerData2); + } + + assertThat(aggregator.getElementCount(), is(2)); + verifyZeroInteractions(session); + } + + /** + * Tests that persist list saving includes correct elements being saved. + */ + @Test + public void saveAllInPersistList() { + aggregator.maxElements = 1; + + TimerData timerData1 = new TimerData(new Timestamp(System.currentTimeMillis()), 10L, 20L, 30L); + TimerData timerData2 = new TimerData(new Timestamp(System.currentTimeMillis()), 100L, 200L, 300L); + + aggregator.processTimerData(timerData1); + aggregator.processTimerData(timerData2); + + aggregator.saveAllInPersistList(); + + ArgumentCaptor argument = ArgumentCaptor.forClass(DatabaseAggregatedTimerData.class); + verify(session, times(1)).insert(argument.capture()); + + assertThat(argument.getValue(), is(instanceOf(DatabaseAggregatedTimerData.class))); + assertThat(argument.getValue().getPlatformIdent(), is(timerData1.getPlatformIdent())); + assertThat(argument.getValue().getSensorTypeIdent(), is(timerData1.getSensorTypeIdent())); + assertThat(argument.getValue().getMethodIdent(), is(timerData1.getMethodIdent())); + } + + /** + * Test for the validity of aggregation. + */ + @Test + public void aggregation() { + long timestampValue = new Date().getTime(); + long platformIdent = new Random().nextLong(); + + final long count = 2; + final double min = 1; + final double max = 2; + final double average = 1.5; + final double duration = 3; + + TimerData timerData = new TimerData(); + timerData.setTimeStamp(new Timestamp(timestampValue)); + timerData.setPlatformIdent(platformIdent); + timerData.setCount(count); + timerData.setExclusiveCount(count); + timerData.setDuration(duration); + timerData.setCpuDuration(duration); + timerData.setExclusiveDuration(duration); + timerData.calculateMin(min); + timerData.calculateCpuMin(min); + timerData.calculateExclusiveMin(min); + timerData.calculateMax(max); + timerData.calculateCpuMax(max); + timerData.calculateExclusiveMax(max); + timerData.setMethodIdent(50L); + + TimerData timerData2 = new TimerData(); + timerData2.setTimeStamp(new Timestamp(timestampValue * 2)); + timerData2.setPlatformIdent(platformIdent); + timerData2.setCount(count); + timerData2.setExclusiveCount(count); + timerData2.setDuration(duration); + timerData2.setCpuDuration(duration); + timerData2.setExclusiveDuration(duration); + timerData2.calculateMin(min); + timerData2.calculateCpuMin(min); + timerData2.calculateExclusiveMin(min); + timerData2.calculateMax(max); + timerData2.calculateCpuMax(max); + timerData2.calculateExclusiveMax(max); + timerData2.setMethodIdent(100L); + + final int elements = 1000; + + for (int i = 0; i < elements / 2; i++) { + aggregator.processTimerData(timerData); + } + + for (int i = 0; i < elements / 2; i++) { + aggregator.processTimerData(timerData2); + } + + aggregator.removeAndPersistAll(); + + verify(session, times(2)).insert(argThat(new ArgumentMatcher() { + @Override + public boolean matches(Object argument) { + if (!DatabaseAggregatedTimerData.class.equals(argument.getClass())) { + return false; + } + TimerData timerData = (TimerData) argument; + + assertThat(timerData.getCount() % count, is(equalTo(0L))); + + assertThat(timerData.getMin(), is(equalTo(min))); + assertThat(timerData.getMax(), is(equalTo(max))); + assertThat(timerData.getAverage(), is(equalTo(average))); + assertThat(timerData.getDuration() / timerData.getCount(), is(equalTo(average))); + + assertThat(timerData.getCpuMin(), is(equalTo(min))); + assertThat(timerData.getCpuMax(), is(equalTo(max))); + assertThat(timerData.getCpuAverage(), is(equalTo(average))); + assertThat(timerData.getCpuDuration() / timerData.getCount(), is(equalTo(average))); + + assertThat(timerData.getExclusiveMin(), is(equalTo(min))); + assertThat(timerData.getExclusiveMax(), is(equalTo(max))); + assertThat(timerData.getExclusiveAverage(), is(equalTo(average))); + + return true; + } + })); + } + + /** + * Verify the zero interactions with setters of {@link TimerData} object passed to the + * aggregator. + */ + @SuppressWarnings("unchecked") + @Test + public void noSetterInteractions() { + TimerData timerData = mock(TimerData.class); + when(timerData.getTimeStamp()).thenReturn(new Timestamp(new Date().getTime())); + when(timerData.getPlatformIdent()).thenReturn(10L); + when(timerData.getMethodIdent()).thenReturn(20L); + + aggregator.processTimerData(timerData); + + verify(timerData, times(0)).setCount(anyLong()); + verify(timerData, times(0)).setCpuDuration(anyDouble()); + verify(timerData, times(0)).calculateCpuMax(anyDouble()); + verify(timerData, times(0)).calculateCpuMin(anyDouble()); + verify(timerData, times(0)).setDuration(anyDouble()); + verify(timerData, times(0)).setExclusiveCount(anyLong()); + verify(timerData, times(0)).setExclusiveDuration(anyDouble()); + verify(timerData, times(0)).calculateExclusiveMax(anyDouble()); + verify(timerData, times(0)).calculateExclusiveMin(anyDouble()); + verify(timerData, times(0)).setId(anyLong()); + verify(timerData, times(0)).calculateMax(anyDouble()); + verify(timerData, times(0)).setMethodIdent(anyLong()); + verify(timerData, times(0)).calculateMin(anyDouble()); + verify(timerData, times(0)).setParameterContentData((Set) anyObject()); + verify(timerData, times(0)).setPlatformIdent(anyLong()); + verify(timerData, times(0)).setSensorTypeIdent(anyLong()); + verify(timerData, times(0)).setTimeStamp((Timestamp) anyObject()); + verify(timerData, times(0)).setVariance(anyDouble()); + } + +} diff --git a/CMR/test/info/novatec/inspectit/cmr/processor/impl/CmrDataProcessorsTest.java b/CMR/test/info/novatec/inspectit/cmr/processor/impl/CmrDataProcessorsTest.java new file mode 100644 index 000000000..ae8be6ee7 --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/processor/impl/CmrDataProcessorsTest.java @@ -0,0 +1,527 @@ +package info.novatec.inspectit.cmr.processor.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Mockito.RETURNS_SMART_NULLS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.cmr.cache.IBuffer; +import info.novatec.inspectit.cmr.cache.IBufferElement; +import info.novatec.inspectit.cmr.dao.impl.TimerDataAggregator; +import info.novatec.inspectit.cmr.processor.AbstractCmrDataProcessor; +import info.novatec.inspectit.cmr.storage.CmrStorageManager; +import info.novatec.inspectit.cmr.util.CacheIdGenerator; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.ExceptionEvent; +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.communication.data.CpuInformationData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.InvocationAwareData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.SystemInformationData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.communication.data.VmArgumentData; +import info.novatec.inspectit.indexing.buffer.IBufferTreeComponent; +import info.novatec.inspectit.indexing.impl.IndexingException; +import info.novatec.inspectit.storage.recording.RecordingState; + +import java.util.Collections; + +import org.hibernate.StatelessSession; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.slf4j.Logger; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Tests for the all cmr data processors we have. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("PMD") +public class CmrDataProcessorsTest { + + @Mock + Logger log; + + @Mock + private StatelessSession session; + + @Mock + private IBuffer buffer; + + @Mock + private CacheIdGenerator cacheIdGenerator; + + @Mock + private IBufferTreeComponent indexingTree; + + @Mock + private CmrStorageManager storageManager; + + @Mock + private TimerDataAggregator timerDataAggregator; + + @Mock + private AbstractCmrDataProcessor chainedProcessor; + + @BeforeMethod + public void init() { + MockitoAnnotations.initMocks(this); + } + + /** + * Tests the {@link BufferInserterCmrProcessor}. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void bufferInserter() { + BufferInserterCmrProcessor processor = new BufferInserterCmrProcessor(); + processor.buffer = buffer; + + // don't fail on null + processor.process((DefaultData) null, session); + verifyZeroInteractions(buffer, session); + + // we don't allow system sensor data + processor.process(new CpuInformationData(), session); + verifyZeroInteractions(buffer, session); + + // we only allow invocation that is a root + InvocationSequenceData invocationSequenceData = mock(InvocationSequenceData.class, RETURNS_SMART_NULLS); + processor.process(invocationSequenceData, session); + verifyZeroInteractions(buffer, session); + + // we don't insert data that's part of invocation + InvocationAwareData invocationAwareData = mock(InvocationAwareData.class); + when(invocationAwareData.isOnlyFoundInInvocations()).thenReturn(false); + when(invocationAwareData.isOnlyFoundOutsideInvocations()).thenReturn(false); + processor.process(invocationAwareData, session); + verifyZeroInteractions(buffer, session); + + // allow other data + invocationAwareData = mock(InvocationAwareData.class); + when(invocationAwareData.isOnlyFoundInInvocations()).thenReturn(false); + when(invocationAwareData.isOnlyFoundOutsideInvocations()).thenReturn(true); + processor.process(invocationAwareData, session); + ArgumentCaptor captor = ArgumentCaptor.forClass(IBufferElement.class); + verify(buffer, times(1)).put(captor.capture()); + verifyZeroInteractions(session); + assertThat(captor.getValue().getObject(), is(equalTo(((Object) invocationAwareData)))); + } + + /** + * Tests the {@link CacheIdGeneratorCmrProcessor}. + */ + @Test + public void cacheIdProcessor() { + CacheIdGeneratorCmrProcessor processor = new CacheIdGeneratorCmrProcessor(); + processor.cacheIdGenerator = cacheIdGenerator; + + // don't fail on null + processor.process((DefaultData) null, session); + verifyZeroInteractions(cacheIdGenerator, session); + + // assign Id otherwise + DefaultData defaultData = mock(DefaultData.class); + processor.process(defaultData, session); + verify(cacheIdGenerator, times(1)).assignObjectAnId(defaultData); + verifyZeroInteractions(session); + } + + /** + * Tests the {@link ExceptionMessageCmrProcessor}. + */ + @Test + public void exceptionMessageProcessor() { + ExceptionMessageCmrProcessor processor = new ExceptionMessageCmrProcessor(); + + // only exceptions + assertThat(processor.canBeProcessed(new TimerData()), is(false)); + + // don't fail on null + processor.process((DefaultData) null, session); + verifyZeroInteractions(session); + + ExceptionSensorData parent = new ExceptionSensorData(); + parent.setErrorMessage("parentMsg"); + ExceptionSensorData child = new ExceptionSensorData(); + child.setErrorMessage("childMsg"); + parent.setChild(child); + + // prove message changing + processor.process(parent, session); + assertThat(parent.getErrorMessage(), is("parentMsg")); + assertThat(child.getErrorMessage(), is("parentMsg")); + } + + /** + * Tests the {@link IndexerCmrProcessor}. + */ + @SuppressWarnings("unchecked") + @Test + public void indexerProcessor() throws IndexingException { + IndexerCmrProcessor processor = new IndexerCmrProcessor(); + processor.log = log; + processor.indexingTree = indexingTree; + + // don't fail on null + processor.process((DefaultData) null, session); + verifyZeroInteractions(log, indexingTree, session); + + // don't allow system sensor data + processor.process(new CpuInformationData(), session); + verifyZeroInteractions(log, indexingTree, session); + + // don't allow invocations + processor.process(new InvocationSequenceData(), session); + verifyZeroInteractions(log, indexingTree, session); + + // don't allow invocation aware data that is not part of invocation + InvocationAwareData invocationAwareData = mock(InvocationAwareData.class); + when(invocationAwareData.isOnlyFoundInInvocations()).thenReturn(false); + when(invocationAwareData.isOnlyFoundOutsideInvocations()).thenReturn(false); + processor.process(invocationAwareData, session); + when(invocationAwareData.isOnlyFoundOutsideInvocations()).thenReturn(true); + processor.process(invocationAwareData, session); + verifyZeroInteractions(log, indexingTree, session); + + // index other data + invocationAwareData = mock(InvocationAwareData.class); + when(invocationAwareData.isOnlyFoundInInvocations()).thenReturn(true); + when(invocationAwareData.isOnlyFoundOutsideInvocations()).thenReturn(false); + processor.process(invocationAwareData, session); + verify(indexingTree, times(1)).put(invocationAwareData); + + // survive indexing exception + when(indexingTree.put(indexingTree.put(invocationAwareData))).thenThrow(IndexingException.class); + processor.process(invocationAwareData, session); + verifyZeroInteractions(session); + } + + /** + * Tests the {@link RecorderCmrProcessor}. + */ + @Test + public void recordProcessor() { + RecorderCmrProcessor processor = new RecorderCmrProcessor(); + processor.storageManager = storageManager; + + // don't fail on null + processor.process((DefaultData) null, session); + verifyZeroInteractions(storageManager, session); + + DefaultData defaultData = mock(DefaultData.class); + + // don't call record if it's not on + when(storageManager.getRecordingState()).thenReturn(RecordingState.OFF); + processor.process(defaultData, session); + verify(storageManager, times(0)).record(defaultData); + + // call record if it's on + when(storageManager.getRecordingState()).thenReturn(RecordingState.ON); + processor.process(defaultData, session); + verify(storageManager, times(1)).record(defaultData); + + verifyZeroInteractions(session); + } + + /** + * Tests the {@link SessionInserterCmrProcessor}. + */ + @Test + public void sessionInserterProcessor() { + // only Timer Data + SessionInserterCmrProcessor processor = new SessionInserterCmrProcessor(Collections.> singletonList(TimerData.class)); + + // don't fail on null + processor.process((DefaultData) null, session); + verifyZeroInteractions(session); + + // don't process wrong classes + processor.process(new SqlStatementData(), session); + processor.process(new HttpTimerData(), session); + verifyZeroInteractions(session); + + // yes for correct class + TimerData timerData = new TimerData(); + processor.process(timerData, session); + verify(session, times(1)).insert(timerData); + } + + /** + * Tests the {@link SqlExclusiveTimeCmrProcessor}. + */ + @Test + public void sqlExclusiveTimeProcessor() { + SqlExclusiveTimeCmrProcessor processor = new SqlExclusiveTimeCmrProcessor(); + + // don't fail on null + processor.process((DefaultData) null, session); + + // only sqls + assertThat(processor.canBeProcessed(new TimerData()), is(false)); + + // make sure exclusive data is set + SqlStatementData sqlStatementData = new SqlStatementData(); + sqlStatementData.setDuration(5d); + processor.process(sqlStatementData, session); + + assertThat(sqlStatementData.getExclusiveCount(), is(1l)); + assertThat(sqlStatementData.getExclusiveDuration(), is(5d)); + assertThat(sqlStatementData.getExclusiveMin(), is(5d)); + assertThat(sqlStatementData.getExclusiveMax(), is(5d)); + + verifyZeroInteractions(session); + } + + /** + * Tests the {@link SystemInformationDataCmrProcessor}. + */ + @Test + public void systemInformationProcessor() { + SystemInformationDataCmrProcessor processor = new SystemInformationDataCmrProcessor(); + + // don't fail on null + processor.process((DefaultData) null, session); + verifyZeroInteractions(session); + + // false data will not be processed + processor.process(new TimerData(), session); + processor.process(new CpuInformationData(), session); + verifyZeroInteractions(session); + + // system information + SystemInformationData systemInformationData = new SystemInformationData(); + systemInformationData.addVMArguments("vmArgumentName", "vmArgumentValue"); + + when(session.insert(systemInformationData)).thenReturn(Long.valueOf(3l)); + processor.process(systemInformationData, session); + verify(session, times(1)).insert(systemInformationData); + + VmArgumentData vmArgumentData = systemInformationData.getVmSet().iterator().next(); + verify(session, times(1)).insert(vmArgumentData); + assertThat(vmArgumentData.getSystemInformationId(), is(3l)); + } + + /** + * Tests the {@link TimerDataChartingCmrProcessor}. + */ + @Test + public void chartingProcessor() { + TimerDataChartingCmrProcessor processor = new TimerDataChartingCmrProcessor(); + processor.timerDataAggregator = timerDataAggregator; + + // don't fail on null + processor.process((DefaultData) null, session); + verifyZeroInteractions(timerDataAggregator, session); + + TimerData timerData = mock(TimerData.class); + HttpTimerData httpTimerData = mock(HttpTimerData.class); + + // first with no charting skip + when(timerData.isCharting()).thenReturn(false); + when(httpTimerData.isCharting()).thenReturn(false); + processor.process(timerData, session); + processor.process(httpTimerData, session); + verifyZeroInteractions(timerDataAggregator, session); + + // then with charting process + when(timerData.isCharting()).thenReturn(true); + when(httpTimerData.isCharting()).thenReturn(true); + processor.process(timerData, session); + processor.process(httpTimerData, session); + // timer to aggregator + verify(timerDataAggregator, times(1)).processTimerData(timerData); + // http to session + verify(session, times(1)).insert(httpTimerData); + verifyNoMoreInteractions(timerDataAggregator, session); + } + + /** + * Timer data processing with {@link InvocationModifierCmrProcessor}. + */ + @Test + public void invocationProcessorTimerData() { + InvocationModifierCmrProcessor processor = new InvocationModifierCmrProcessor(Collections.singletonList(chainedProcessor)); + + InvocationSequenceData parent = new InvocationSequenceData(); + parent.setId(10L); + TimerData parentTimer = new TimerData(); + parentTimer.setCount(1L); + parentTimer.setDuration(2L); + parent.setTimerData(parentTimer); + + InvocationSequenceData child = new InvocationSequenceData(); + child.setId(20L); + TimerData childTimer = new TimerData(); + childTimer.setCount(1L); + childTimer.setDuration(1L); + child.setTimerData(childTimer); + child.setParentSequence(parent); + + parent.setNestedSequences(Collections.singletonList(child)); + + processor.process(parent, session); + + // correctly passed to the chained + verify(chainedProcessor, times(1)).process(parentTimer, session); + verify(chainedProcessor, times(1)).process(child, session); + verify(chainedProcessor, times(1)).process(childTimer, session); + verifyNoMoreInteractions(chainedProcessor); + verifyZeroInteractions(session); + + // exclusive times in timers are set + assertThat(parentTimer.getExclusiveDuration(), is(1d)); + assertThat(childTimer.getExclusiveDuration(), is(1d)); + + // invocation parent is set correctly + assertThat(parentTimer.isOnlyFoundInInvocations(), is(true)); + assertThat(parentTimer.getInvocationParentsIdSet(), hasSize(1)); + assertThat(parentTimer.getInvocationParentsIdSet(), hasItem(10L)); + + // not that every timer must point to the root invocation + assertThat(childTimer.isOnlyFoundInInvocations(), is(true)); + assertThat(childTimer.getInvocationParentsIdSet(), hasSize(1)); + assertThat(childTimer.getInvocationParentsIdSet(), hasItem(10L)); + } + + /** + * Sql data processing with {@link InvocationModifierCmrProcessor}. + */ + @Test + public void invocationProcessorSqlData() { + InvocationModifierCmrProcessor processor = new InvocationModifierCmrProcessor(Collections.singletonList(chainedProcessor)); + + InvocationSequenceData parent = new InvocationSequenceData(); + parent.setId(10L); + TimerData parentTimer = new TimerData(); + parentTimer.setCount(1L); + parentTimer.setDuration(2L); + parent.setTimerData(parentTimer); + + InvocationSequenceData child = new InvocationSequenceData(); + child.setId(20L); + SqlStatementData sql = new SqlStatementData(); + sql.setCount(1L); + sql.setDuration(1L); + child.setSqlStatementData(sql); + child.setParentSequence(parent); + + parent.setNestedSequences(Collections.singletonList(child)); + + processor.process(parent, session); + + // correctly passed to the chained + verify(chainedProcessor, times(1)).process(parentTimer, session); + verify(chainedProcessor, times(1)).process(child, session); + verify(chainedProcessor, times(1)).process(sql, session); + verifyNoMoreInteractions(chainedProcessor); + verifyZeroInteractions(session); + + // root has info about sql + assertThat(parent.isNestedSqlStatements(), is(true)); + + // exclusive times in parent timer is set + assertThat(parentTimer.getExclusiveDuration(), is(1d)); + + // sql has correct invocation affiliation + assertThat(sql.isOnlyFoundInInvocations(), is(true)); + assertThat(sql.getInvocationParentsIdSet(), hasSize(1)); + assertThat(sql.getInvocationParentsIdSet(), hasItem(10L)); + } + + /** + * Simple exception processing with {@link InvocationModifierCmrProcessor}. + */ + @Test + public void invocationProcessorOneExceptionData() { + InvocationModifierCmrProcessor processor = new InvocationModifierCmrProcessor(Collections.singletonList(chainedProcessor)); + ExceptionMessageCmrProcessor exceptionMessageCmrProcessor = mock(ExceptionMessageCmrProcessor.class); + processor.exceptionMessageCmrProcessor = exceptionMessageCmrProcessor; + + InvocationSequenceData parent = new InvocationSequenceData(); + parent.setId(10L); + + InvocationSequenceData child = new InvocationSequenceData(); + child.setId(20L); + ExceptionSensorData exceptionSensorData = new ExceptionSensorData(); + exceptionSensorData.setExceptionEvent(ExceptionEvent.CREATED); + exceptionSensorData.setThrowableIdentityHashCode(1L); + child.setExceptionSensorDataObjects(Collections.singletonList(exceptionSensorData)); + child.setParentSequence(parent); + + parent.setNestedSequences(Collections.singletonList(child)); + + processor.process(parent, session); + + // correctly passed to the chained + verify(chainedProcessor, times(1)).process(child, session); + verify(chainedProcessor, times(1)).process(exceptionSensorData, session); + verify(exceptionMessageCmrProcessor, times(1)).process(exceptionSensorData, session); + verifyNoMoreInteractions(chainedProcessor, exceptionMessageCmrProcessor); + verifyZeroInteractions(session); + + // root has info about sql + assertThat(parent.isNestedExceptions(), is(true)); + + // sql has correct invocation affiliation + assertThat(exceptionSensorData.isOnlyFoundInInvocations(), is(true)); + assertThat(exceptionSensorData.getInvocationParentsIdSet(), hasSize(1)); + assertThat(exceptionSensorData.getInvocationParentsIdSet(), hasItem(10L)); + } + + /** + * When we have not created exception event in exception object. + */ + @Test + public void invocationProcessorNotCreatedExceptionData() { + InvocationModifierCmrProcessor processor = new InvocationModifierCmrProcessor(Collections.singletonList(chainedProcessor)); + ExceptionMessageCmrProcessor exceptionMessageCmrProcessor = mock(ExceptionMessageCmrProcessor.class); + processor.exceptionMessageCmrProcessor = exceptionMessageCmrProcessor; + + InvocationSequenceData parent = new InvocationSequenceData(); + parent.setId(10L); + + InvocationSequenceData child = new InvocationSequenceData(); + child.setId(20L); + ExceptionSensorData exceptionSensorData = new ExceptionSensorData(); + exceptionSensorData.setExceptionEvent(ExceptionEvent.PASSED); + exceptionSensorData.setThrowableIdentityHashCode(1L); + child.setExceptionSensorDataObjects(Collections.singletonList(exceptionSensorData)); + child.setParentSequence(parent); + + parent.setNestedSequences(Collections.singletonList(child)); + + processor.process(parent, session); + + // correctly passed to the chained + verify(chainedProcessor, times(1)).process(child, session); + verify(chainedProcessor, times(0)).process(exceptionSensorData, session); + verify(exceptionMessageCmrProcessor, times(0)).process(exceptionSensorData, session); + verifyNoMoreInteractions(chainedProcessor, exceptionMessageCmrProcessor); + verifyZeroInteractions(session); + + // root has info about sql + assertThat(parent.isNestedExceptions(), is(nullValue())); + + // sql has correct invocation affiliation + assertThat(exceptionSensorData.isOnlyFoundInInvocations(), is(false)); + assertThat(exceptionSensorData.getInvocationParentsIdSet(), is(empty())); + } + +} diff --git a/CMR/test/info/novatec/inspectit/cmr/property/PropertyIntegrationTest.java b/CMR/test/info/novatec/inspectit/cmr/property/PropertyIntegrationTest.java new file mode 100644 index 000000000..6114fb35b --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/property/PropertyIntegrationTest.java @@ -0,0 +1,81 @@ +package info.novatec.inspectit.cmr.property; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import info.novatec.inspectit.cmr.cache.impl.AtomicBuffer; +import info.novatec.inspectit.cmr.cache.impl.BufferProperties; +import info.novatec.inspectit.cmr.property.configuration.Configuration; +import info.novatec.inspectit.cmr.property.configuration.SingleProperty; +import info.novatec.inspectit.cmr.property.update.configuration.ConfigurationUpdate; +import info.novatec.inspectit.cmr.test.AbstractTransactionalTestNGLogSupport; + +import java.io.IOException; +import java.nio.file.Files; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; + +/** + * Test that some of the properties updates are working fine in integration mode. + *

+ * Only important properties changes are taken into account here. + * + * @author Ivan Senic + * + */ +@ContextConfiguration(locations = { "classpath:spring/spring-context-global.xml", "classpath:spring/spring-context-database.xml", "classpath:spring/spring-context-beans.xml", + "classpath:spring/spring-context-processors.xml", "classpath:spring/spring-context-storage-test.xml" }) +@SuppressWarnings("PMD") +public class PropertyIntegrationTest extends AbstractTransactionalTestNGLogSupport { + + @Autowired + private PropertyManager propertyManager; + + @Autowired + private BufferProperties bufferProperties; + + @Autowired + private AtomicBuffer buffer; + + @Test + public void increaseBufferSizeWithOccupancy() throws Exception { + Configuration configuration = propertyManager.getConfiguration(); + SingleProperty maxOldSpaceOcc = configuration.forLogicalName("buffer.maxOldSpaceOccupancy"); + SingleProperty minOldSpaceOcc = configuration.forLogicalName("buffer.minOldSpaceOccupancy"); + long oldBufferSize = bufferProperties.getInitialBufferSize(); + + ConfigurationUpdate configurationUpdate = new ConfigurationUpdate(); + configurationUpdate.addPropertyUpdate(maxOldSpaceOcc.createAndValidatePropertyUpdate(Float.valueOf(maxOldSpaceOcc.getValue().floatValue() + 0.05f))); + configurationUpdate.addPropertyUpdate(minOldSpaceOcc.createAndValidatePropertyUpdate(Float.valueOf(minOldSpaceOcc.getValue().floatValue() + 0.05f))); + + propertyManager.updateConfiguration(configurationUpdate, false); + long newBufferSize = bufferProperties.getInitialBufferSize(); + assertThat(newBufferSize, is(greaterThan(oldBufferSize))); + assertThat(newBufferSize, is(buffer.getMaxSize())); + } + + @Test + public void increaseExpansionRate() throws Exception { + long bufferSize = bufferProperties.getInitialBufferSize(); + + Configuration configuration = propertyManager.getConfiguration(); + SingleProperty maxExpansionRate = configuration.forLogicalName("buffer.maxObjectExpansionRate"); + float oldExpansionrate = bufferProperties.getObjectSecurityExpansionRate(bufferSize); + + ConfigurationUpdate configurationUpdate = new ConfigurationUpdate(); + configurationUpdate.addPropertyUpdate(maxExpansionRate.createAndValidatePropertyUpdate(Float.valueOf(maxExpansionRate.getValue().floatValue() + 0.1f))); + + propertyManager.updateConfiguration(configurationUpdate, false); + float newExpansionrate = bufferProperties.getObjectSecurityExpansionRate(bufferSize); + assertThat(newExpansionrate, is(greaterThan(oldExpansionrate))); + } + + @AfterMethod + public void deleteUpdateFiles() throws IOException { + Files.deleteIfExists(propertyManager.getConfigurationUpdatePath()); + } + +} diff --git a/CMR/test/info/novatec/inspectit/cmr/property/PropertyManagerTest.java b/CMR/test/info/novatec/inspectit/cmr/property/PropertyManagerTest.java new file mode 100644 index 000000000..b14ea53d5 --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/property/PropertyManagerTest.java @@ -0,0 +1,307 @@ +package info.novatec.inspectit.cmr.property; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.cmr.jaxb.JAXBTransformator; +import info.novatec.inspectit.cmr.property.configuration.AbstractProperty; +import info.novatec.inspectit.cmr.property.configuration.Configuration; +import info.novatec.inspectit.cmr.property.configuration.PropertySection; +import info.novatec.inspectit.cmr.property.configuration.SingleProperty; +import info.novatec.inspectit.cmr.property.configuration.impl.LongProperty; +import info.novatec.inspectit.cmr.property.configuration.impl.StringProperty; +import info.novatec.inspectit.cmr.property.configuration.validation.PropertyValidation; +import info.novatec.inspectit.cmr.property.update.AbstractPropertyUpdate; +import info.novatec.inspectit.cmr.property.update.IPropertyUpdate; +import info.novatec.inspectit.cmr.property.update.configuration.ConfigurationUpdate; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; + +import javax.xml.bind.JAXBException; + +import org.apache.commons.collections.MapUtils; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.xml.sax.SAXException; + +@SuppressWarnings("PMD") +public class PropertyManagerTest { + + @InjectMocks + private PropertyManager propertyManager; + + @Mock + private ConfigurationUpdate configurationUpdate; + + @Mock + private Configuration configuration; + + @Mock + private PropertyUpdateExecutor propertyUpdateExecutor; + + @Mock + private JAXBTransformator transformator; + + @BeforeMethod + public void init() { + MockitoAnnotations.initMocks(this); + } + + /** + * Tests that the loading of default configuration and updates can be executed with no + * exceptions. + */ + @Test + public void loadDefaultConfiguration() throws JAXBException, IOException, SAXException { + propertyManager.loadConfigurationAndUpdates(); + } + + /** + * Test that the {@link Properties} returned by the {@link PropertyManager} are correctly take + * from {@link Configuration}. + */ + @Test + public void propertyInDefaultConfiguration() throws JAXBException, IOException, SAXException { + doReturn(configuration).when(transformator).unmarshall(Mockito. anyObject(), Mockito. anyObject(), eq(Configuration.class)); + doReturn(null).when(transformator).unmarshall(Mockito. anyObject(), Mockito. anyObject(), eq(ConfigurationUpdate.class)); + when(configuration.validate()).thenReturn(Collections. emptyMap()); + + SingleProperty property = mock(SingleProperty.class); + + Answer answer = new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Properties properties = (Properties) invocation.getArguments()[0]; + properties.put("property1", "value1"); + return null; + } + }; + doAnswer(answer).when(property).register(Mockito. anyObject()); + when(configuration.getAllProperties()).thenReturn(Collections. singleton(property)); + + Properties properties = propertyManager.getProperties(); + assertThat(properties.getProperty("property1"), is(equalTo("value1"))); + assertThat(properties.size(), is(1)); + } + + /** + * Test that if validation fails for the property it won't be included in the {@link Properties} + * returned by {@link PropertyManager}. + */ + @SuppressWarnings("unchecked") + @Test + public void propertyNotValidInDefaultConfiguration() throws JAXBException, IOException, SAXException { + doReturn(configuration).when(transformator).unmarshall(Mockito. anyObject(), Mockito. anyObject(), eq(Configuration.class)); + doReturn(null).when(transformator).unmarshall(Mockito. anyObject(), Mockito. anyObject(), eq(ConfigurationUpdate.class)); + + SingleProperty property = mock(SingleProperty.class); + PropertyValidation propertyValidation = mock(PropertyValidation.class, Mockito.RETURNS_SMART_NULLS); + when(configuration.getAllProperties()).thenReturn(Collections. singleton(property)); + when(configuration.validate()).thenReturn(MapUtils.putAll(new HashMap(), new Object[][] { { property, propertyValidation } })); + + Properties properties = propertyManager.getProperties(); + verify(property, times(0)).register(Mockito. anyObject()); + assertThat(properties.size(), is(0)); + } + + /** + * Test that {@link Properties} will hold an updated value of the property if it's included in + * the {@link ConfigurationUpdate}. + */ + @Test + @SuppressWarnings("unchecked") + public void savedPropertyUpdateFromTheDefaultConfiguration() throws JAXBException, IOException, SAXException { + Configuration configuration = new Configuration(); + PropertySection section = new PropertySection(); + SingleProperty property = new StringProperty("", "", "property1", "value1", false, false); + section.addProperty(property); + configuration.addSection(section); + + doReturn(configuration).when(transformator).unmarshall(Mockito. anyObject(), Mockito. anyObject(), eq(Configuration.class)); + doReturn(configurationUpdate).when(transformator).unmarshall(Mockito. anyObject(), Mockito. anyObject(), eq(ConfigurationUpdate.class)); + + AbstractPropertyUpdate propertyUpdate = mock(AbstractPropertyUpdate.class); + when(propertyUpdate.getPropertyLogicalName()).thenReturn("property1"); + when(propertyUpdate.getUpdateValue()).thenReturn("updatedValue"); + when(configurationUpdate.getPropertyUpdates()).thenReturn(Collections.> singleton(propertyUpdate)); + + Properties properties = propertyManager.getProperties(); + assertThat(properties.getProperty("property1"), is(equalTo("updatedValue"))); + assertThat(properties.size(), is(1)); + } + + /** + * Test that not matching property update will not be taken into account. + */ + @Test + @SuppressWarnings("unchecked") + public void savedPropertyUpdateNotValid() throws JAXBException, IOException, SAXException { + Configuration configuration = new Configuration(); + PropertySection section = new PropertySection(); + SingleProperty property = new LongProperty("", "", "property1", 10L, false, false); + section.addProperty(property); + configuration.addSection(section); + + doReturn(configuration).when(transformator).unmarshall(Mockito. anyObject(), Mockito. anyObject(), eq(Configuration.class)); + doReturn(configurationUpdate).when(transformator).unmarshall(Mockito. anyObject(), Mockito. anyObject(), eq(ConfigurationUpdate.class)); + + AbstractPropertyUpdate propertyUpdate = mock(AbstractPropertyUpdate.class); + when(propertyUpdate.getPropertyLogicalName()).thenReturn("property1"); + when(propertyUpdate.getUpdateValue()).thenReturn("updatedValue"); + when(configurationUpdate.getPropertyUpdates()).thenReturn(Collections.> singleton(propertyUpdate)); + + Properties properties = propertyManager.getProperties(); + assertThat(Long.valueOf(properties.getProperty("property1")), is(10L)); + assertThat(properties.size(), is(1)); + } + + /** + * Check that Exception will be thrown if the {@link ConfigurationUpdate} has update that can + * not be applied. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test(expectedExceptions = { Exception.class }) + public void noUpdateIfCanNotUpdateProperty() throws Exception { + doReturn(configuration).when(transformator).unmarshall(Mockito. anyObject(), Mockito. anyObject(), eq(Configuration.class)); + doReturn(null).when(transformator).unmarshall(Mockito. anyObject(), Mockito. anyObject(), eq(ConfigurationUpdate.class)); + + ConfigurationUpdate configurationUpdate = mock(ConfigurationUpdate.class); + AbstractPropertyUpdate propertyUpdate = mock(AbstractPropertyUpdate.class); + when(propertyUpdate.getPropertyLogicalName()).thenReturn("property1"); + when(configurationUpdate.getPropertyUpdates()).thenReturn(Collections.> singleton(propertyUpdate)); + + SingleProperty singleProperty = mock(SingleProperty.class); + when(singleProperty.canUpdate(propertyUpdate)).thenReturn(false); + when(configuration.forLogicalName(Mockito. anyObject())).thenReturn(singleProperty); + + propertyManager.loadConfigurationAndUpdates(); + propertyManager.updateConfiguration(configurationUpdate, false); + } + + /** + * Check that Exception will be thrown if the {@link ConfigurationUpdate} has update for non + * existing property. + */ + @SuppressWarnings("unchecked") + @Test(expectedExceptions = { Exception.class }) + public void noUpdateIfPropertyNotFound() throws Exception { + doReturn(configuration).when(transformator).unmarshall(Mockito. anyObject(), Mockito. anyObject(), eq(Configuration.class)); + doReturn(null).when(transformator).unmarshall(Mockito. anyObject(), Mockito. anyObject(), eq(ConfigurationUpdate.class)); + when(configuration.forLogicalName(Mockito. anyObject())).thenReturn(null); + ConfigurationUpdate configurationUpdate = mock(ConfigurationUpdate.class); + AbstractPropertyUpdate propertyUpdate = mock(AbstractPropertyUpdate.class); + when(propertyUpdate.getPropertyLogicalName()).thenReturn("property1"); + when(configurationUpdate.getPropertyUpdates()).thenReturn(Collections.> singleton(propertyUpdate)); + + propertyManager.loadConfigurationAndUpdates(); + propertyManager.updateConfiguration(configurationUpdate, false); + } + + /** + * Check that property will be correctly updated during runtime. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void runtimePropertyUpdate() throws Exception { + Configuration configuration = new Configuration(); + PropertySection section = new PropertySection(); + SingleProperty property = new LongProperty("", "", "property1", 10L, false, false); + section.addProperty(property); + configuration.addSection(section); + + doReturn(configuration).when(transformator).unmarshall(Mockito. anyObject(), Mockito. anyObject(), eq(Configuration.class)); + doReturn(null).when(transformator).unmarshall(Mockito. anyObject(), Mockito. anyObject(), eq(ConfigurationUpdate.class)); + doNothing().when(transformator).marshall(Mockito. anyObject(), any(), anyString()); + + ConfigurationUpdate configurationUpdate = mock(ConfigurationUpdate.class); + AbstractPropertyUpdate propertyUpdate = mock(AbstractPropertyUpdate.class); + when(propertyUpdate.getPropertyLogicalName()).thenReturn("property1"); + when(propertyUpdate.getUpdateValue()).thenReturn(20L); + when(configurationUpdate.getPropertyUpdates()).thenReturn(Collections.> singleton(propertyUpdate)); + + propertyManager.loadConfigurationAndUpdates(); + propertyManager.updateConfiguration(configurationUpdate, false); + + assertThat(property.getValue(), is(20L)); + ArgumentCaptor captor = ArgumentCaptor.forClass(List.class); + verify(propertyUpdateExecutor, times(1)).executePropertyUpdates(captor.capture()); + List> list = captor.getValue(); + assertThat(list, hasSize(1)); + assertThat(list, hasItem(property)); + + // confirm configuration update write + verify(transformator, times(1)).marshall(Mockito. anyObject(), eq(configurationUpdate), anyString()); + } + + /** + * Confirm restore to default of already existing property. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void runtimePropertyUpdateOfAlreadyUpdatedProperty() throws Exception { + Configuration configuration = new Configuration(); + PropertySection section = new PropertySection(); + SingleProperty property = new LongProperty("", "", "property1", 10L, false, false); + section.addProperty(property); + configuration.addSection(section); + + ConfigurationUpdate configurationUpdate = mock(ConfigurationUpdate.class, Mockito.RETURNS_MOCKS); + AbstractPropertyUpdate propertyUpdate = mock(AbstractPropertyUpdate.class); + when(propertyUpdate.getPropertyLogicalName()).thenReturn("property1"); + when(propertyUpdate.getUpdateValue()).thenReturn(20L); + Set> propertyUpdates = Mockito.spy(new HashSet>()); + propertyUpdates.add(propertyUpdate); + when(configurationUpdate.getPropertyUpdates()).thenReturn(propertyUpdates); + + doReturn(configuration).when(transformator).unmarshall(Mockito. anyObject(), Mockito. anyObject(), eq(Configuration.class)); + doReturn(configurationUpdate).when(transformator).unmarshall(Mockito. anyObject(), Mockito. anyObject(), eq(ConfigurationUpdate.class)); + doNothing().when(transformator).marshall(Mockito. anyObject(), any(), anyString()); + + ConfigurationUpdate configurationUpdateRuntime = mock(ConfigurationUpdate.class); + AbstractPropertyUpdate propertyUpdateRuntime = mock(AbstractPropertyUpdate.class); + when(propertyUpdateRuntime.getPropertyLogicalName()).thenReturn("property1"); + when(propertyUpdateRuntime.getUpdateValue()).thenReturn(10L); // set back to default value + when(configurationUpdateRuntime.getPropertyUpdates()).thenReturn(Collections.> singleton(propertyUpdateRuntime)); + + propertyManager.getProperties(); + propertyManager.updateConfiguration(configurationUpdateRuntime, false); + + // confirm property update and value + assertThat(property.getValue(), is(10L)); // returned to the default value + ArgumentCaptor captor = ArgumentCaptor.forClass(List.class); + verify(propertyUpdateExecutor, times(1)).executePropertyUpdates(captor.capture()); + List> list = captor.getValue(); + assertThat(list, hasSize(1)); + assertThat(list, hasItem(property)); + + // confirm merging of configurations and write + verify(configurationUpdate, times(1)).merge(configurationUpdateRuntime, true); + verify(transformator, times(1)).marshall(Mockito. anyObject(), eq(configurationUpdate), anyString()); + } +} diff --git a/CMR/test/info/novatec/inspectit/cmr/property/PropertyUpdateExecutorTest.java b/CMR/test/info/novatec/inspectit/cmr/property/PropertyUpdateExecutorTest.java new file mode 100644 index 000000000..38572ea2a --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/property/PropertyUpdateExecutorTest.java @@ -0,0 +1,221 @@ +package info.novatec.inspectit.cmr.property; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import info.novatec.inspectit.cmr.property.configuration.SingleProperty; +import info.novatec.inspectit.cmr.property.spring.PropertyUpdate; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class PropertyUpdateExecutorTest { + + private static final String PROPERTY1 = "property1"; + + private static final String PROPERTY2 = "property2"; + + private PropertyUpdateExecutor propertyUpdateExecutor; + + @Mock + private ConfigurableListableBeanFactory beanFactory; + + @Mock + private TypeConverter typeConverter; + + @BeforeMethod + public void init() { + MockitoAnnotations.initMocks(this); + Mockito.when(beanFactory.getTypeConverter()).thenReturn(typeConverter); + propertyUpdateExecutor = new PropertyUpdateExecutor(); + propertyUpdateExecutor.setBeanFactory(beanFactory); + } + + @Test + @SuppressWarnings("unchecked") + public void fieldOnePropertyNotMatchingType() { + SingleProperty property = Mockito.mock(SingleProperty.class); + Mockito.when(property.getLogicalName()).thenReturn(PROPERTY1); + Mockito.when(property.getValue()).thenReturn(Integer.valueOf(10)); + Mockito.when(typeConverter.convertIfNecessary(Integer.valueOf(10), int.class)).thenReturn(10); + + FieldTestClass fieldTestClass = new FieldTestClass(); + propertyUpdateExecutor.postProcessAfterInitialization(fieldTestClass, "testClass"); + propertyUpdateExecutor.executePropertyUpdates(Collections.> singletonList(property)); + + assertThat(fieldTestClass.update1, is(10)); + assertThat(fieldTestClass.update2, is(nullValue())); + assertThat(fieldTestClass.noUpdate, is(nullValue())); + } + + @Test + @SuppressWarnings("unchecked") + public void fieldOnePropertyMatchingType() { + SingleProperty property = Mockito.mock(SingleProperty.class); + Mockito.when(property.getLogicalName()).thenReturn(PROPERTY2); + Mockito.when(property.getValue()).thenReturn(Long.valueOf(10)); + + FieldTestClass fieldTestClass = new FieldTestClass(); + propertyUpdateExecutor.postProcessAfterInitialization(fieldTestClass, "testClass"); + propertyUpdateExecutor.executePropertyUpdates(Collections.> singletonList(property)); + + Mockito.verifyZeroInteractions(typeConverter); + assertThat(fieldTestClass.update1, is(0)); + assertThat(fieldTestClass.update2, is(10L)); + assertThat(fieldTestClass.noUpdate, is(nullValue())); + } + + @Test + @SuppressWarnings("unchecked") + public void fieldTwoProperties() { + SingleProperty property1 = Mockito.mock(SingleProperty.class); + Mockito.when(property1.getLogicalName()).thenReturn(PROPERTY1); + Mockito.when(property1.getValue()).thenReturn(Integer.valueOf(10)); + Mockito.when(typeConverter.convertIfNecessary(Integer.valueOf(10), int.class)).thenReturn(10); + SingleProperty property2 = Mockito.mock(SingleProperty.class); + Mockito.when(property2.getLogicalName()).thenReturn(PROPERTY2); + Mockito.when(property2.getValue()).thenReturn(Long.valueOf(10)); + List> list = new ArrayList<>(); + list.add(property1); + list.add(property2); + + FieldTestClass fieldTestClass = new FieldTestClass(); + propertyUpdateExecutor.postProcessAfterInitialization(fieldTestClass, "testClass"); + propertyUpdateExecutor.executePropertyUpdates(list); + + assertThat(fieldTestClass.update1, is(10)); + assertThat(fieldTestClass.update2, is(10L)); + assertThat(fieldTestClass.noUpdate, is(nullValue())); + } + + @Test + @SuppressWarnings("unchecked") + public void fieldTwoInstances() { + SingleProperty property = Mockito.mock(SingleProperty.class); + Mockito.when(property.getLogicalName()).thenReturn(PROPERTY2); + Mockito.when(property.getValue()).thenReturn(Long.valueOf(10)); + + FieldTestClass fieldTestClass1 = new FieldTestClass(); + FieldTestClass fieldTestClass2 = new FieldTestClass(); + propertyUpdateExecutor.postProcessAfterInitialization(fieldTestClass1, "testClass"); + propertyUpdateExecutor.postProcessAfterInitialization(fieldTestClass2, "testClass"); + propertyUpdateExecutor.executePropertyUpdates(Collections.> singletonList(property)); + + Mockito.verifyZeroInteractions(typeConverter); + assertThat(fieldTestClass1.update1, is(0)); + assertThat(fieldTestClass1.update2, is(10L)); + assertThat(fieldTestClass1.noUpdate, is(nullValue())); + assertThat(fieldTestClass2.update1, is(0)); + assertThat(fieldTestClass2.update2, is(10L)); + assertThat(fieldTestClass2.noUpdate, is(nullValue())); + } + + @Test + public void methodOneProperty() { + SingleProperty property = Mockito.mock(SingleProperty.class); + Mockito.when(property.getLogicalName()).thenReturn(PROPERTY1); + + MethodTestClass testClassInstance = new MethodTestClass(); + propertyUpdateExecutor.postProcessAfterInitialization(testClassInstance, "testClass"); + propertyUpdateExecutor.executePropertyUpdates(Collections.> singletonList(property)); + + assertThat(testClassInstance.a, is(2)); + } + + @Test + public void methodTwoProperties() { + SingleProperty property1 = Mockito.mock(SingleProperty.class); + Mockito.when(property1.getLogicalName()).thenReturn(PROPERTY1); + SingleProperty property2 = Mockito.mock(SingleProperty.class); + Mockito.when(property2.getLogicalName()).thenReturn(PROPERTY2); + List> list = new ArrayList<>(); + list.add(property1); + list.add(property2); + + MethodTestClass testClassInstance = new MethodTestClass(); + propertyUpdateExecutor.postProcessAfterInitialization(testClassInstance, "testClass"); + propertyUpdateExecutor.executePropertyUpdates(list); + + assertThat(testClassInstance.a, is(3)); + } + + @Test + public void methodNoProperties() { + SingleProperty property = Mockito.mock(SingleProperty.class); + Mockito.when(property.getLogicalName()).thenReturn("someOtherProperty"); + + MethodTestClass testClassInstance = new MethodTestClass(); + propertyUpdateExecutor.postProcessAfterInitialization(testClassInstance, "testClass"); + propertyUpdateExecutor.executePropertyUpdates(Collections.> singletonList(property)); + + assertThat(testClassInstance.a, is(0)); + } + + @Test + public void methodTwoTestInstances() { + SingleProperty property1 = Mockito.mock(SingleProperty.class); + Mockito.when(property1.getLogicalName()).thenReturn(PROPERTY1); + SingleProperty property2 = Mockito.mock(SingleProperty.class); + Mockito.when(property2.getLogicalName()).thenReturn(PROPERTY2); + List> list = new ArrayList<>(); + list.add(property1); + list.add(property2); + + MethodTestClass testClassInstance = new MethodTestClass(); + MethodTestClass testClassInstance2 = new MethodTestClass(); + propertyUpdateExecutor.postProcessAfterInitialization(testClassInstance, "testClass"); + propertyUpdateExecutor.postProcessAfterInitialization(testClassInstance2, "testClass2"); + propertyUpdateExecutor.executePropertyUpdates(list); + + assertThat(testClassInstance.a, is(3)); + assertThat(testClassInstance2.a, is(3)); + } + + public static final class MethodTestClass { + + public int a; + + @PropertyUpdate(properties = { PROPERTY1 }) + public void property1() { + a++; + } + + @PropertyUpdate(properties = { PROPERTY2 }) + public void property2() { + a++; + } + + @PropertyUpdate(properties = { PROPERTY1, PROPERTY2 }) + public void property1And2() { + a++; + } + + @PropertyUpdate + public void noPropertiesDefined() { + a++; + } + } + + public static final class FieldTestClass { + + @Value("${" + PROPERTY1 + "}") + public int update1; + + @Value("${" + PROPERTY2 + "}") + public Long update2; + + public String noUpdate; + + } +} diff --git a/CMR/test/info/novatec/inspectit/cmr/service/AgentStorageServiceTest.java b/CMR/test/info/novatec/inspectit/cmr/service/AgentStorageServiceTest.java new file mode 100644 index 000000000..5b6e93ef3 --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/service/AgentStorageServiceTest.java @@ -0,0 +1,95 @@ +package info.novatec.inspectit.cmr.service; + +import info.novatec.inspectit.cmr.test.AbstractTestNGLogSupport; +import info.novatec.inspectit.cmr.util.AgentStatusDataProvider; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.TimerData; + +import java.lang.ref.SoftReference; +import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Tests the agent storage service. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("PMD") +public class AgentStorageServiceTest extends AbstractTestNGLogSupport { + + /** + * Service to be tested. + */ + private AgentStorageService agentStorageService; + + /** + * {@link AgentStatusDataProvider}. + */ + @Mock + private AgentStatusDataProvider agentStatusDataProvider; + + @Mock + private ICmrManagementService cmrManagementService; + + /** + * Initializes the mocks. + */ + @BeforeMethod + public void init() { + MockitoAnnotations.initMocks(this); + agentStorageService = new AgentStorageService(new ArrayBlockingQueue>>(1)); + agentStorageService.platformIdentDateSaver = agentStatusDataProvider; + agentStorageService.cmrManagementService = cmrManagementService; + agentStorageService.log = LoggerFactory.getLogger(AgentStorageService.class); + } + + /** + * Proves that the data will be dropped after the timeout if there is no place in the queue and + * amount of dropped data be remembered. + * + * @throws RemoteException + * If {@link RemoteException} occurs. + */ + @Test + public void dropDataAfterTimeout() throws RemoteException { + List dataList = new ArrayList(); + TimerData timerData = new TimerData(); + timerData.setPlatformIdent(1L); + dataList.add(timerData); + + agentStorageService.addDataObjects(dataList); + agentStorageService.addDataObjects(dataList); + + Mockito.verify(agentStatusDataProvider, Mockito.times(2)).registerDataSent(1L); + Mockito.verify(cmrManagementService, Mockito.times(1)).addDroppedDataCount(dataList.size()); + } + + /** + * Provides that data will be processed if there is place in the queue. + * + * @throws RemoteException + * If {@link RemoteException} occurs. + */ + @Test + public void acceptData() throws RemoteException { + List dataList = new ArrayList(); + TimerData timerData = new TimerData(); + timerData.setPlatformIdent(1L); + dataList.add(timerData); + + agentStorageService.addDataObjects(dataList); + + Mockito.verify(agentStatusDataProvider, Mockito.times(1)).registerDataSent(1L); + Mockito.verifyZeroInteractions(cmrManagementService); + } +} diff --git a/CMR/test/info/novatec/inspectit/cmr/service/GlobalDataAccessServiceTest.java b/CMR/test/info/novatec/inspectit/cmr/service/GlobalDataAccessServiceTest.java new file mode 100644 index 000000000..ba995136a --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/service/GlobalDataAccessServiceTest.java @@ -0,0 +1,105 @@ +package info.novatec.inspectit.cmr.service; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.cmr.dao.DefaultDataDao; +import info.novatec.inspectit.cmr.dao.PlatformIdentDao; +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.cmr.service.exception.ServiceException; +import info.novatec.inspectit.cmr.test.AbstractTestNGLogSupport; +import info.novatec.inspectit.cmr.util.AgentStatusDataProvider; +import info.novatec.inspectit.communication.data.cmr.AgentStatusData; +import info.novatec.inspectit.communication.data.cmr.AgentStatusData.AgentConnection; + +import java.util.HashMap; +import java.util.Map; + +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class GlobalDataAccessServiceTest extends AbstractTestNGLogSupport { + + /** + * Class under test. + */ + private GlobalDataAccessService globalDataAccessService; + + @Mock + private PlatformIdentDao platformIdentDao; + + @Mock + private DefaultDataDao defaultDataDao; + + @Mock + private AgentStatusDataProvider agentStatusProvider; + + /** + * Initializes mocks. Has to run before each test so that mocks are clear. + */ + @BeforeMethod + public void init() { + MockitoAnnotations.initMocks(this); + + globalDataAccessService = new GlobalDataAccessService(); + globalDataAccessService.platformIdentDao = platformIdentDao; + globalDataAccessService.agentStatusProvider = agentStatusProvider; + globalDataAccessService.defaultDataDao = defaultDataDao; + globalDataAccessService.log = LoggerFactory.getLogger(GlobalDataAccessService.class); + } + + /** + * No delete enabled when platform ident can not be found. + */ + @Test(expectedExceptions = { ServiceException.class }) + public void testNonExistingAgentDelete() throws ServiceException { + long platformId = 10L; + when(platformIdentDao.load(Long.valueOf(platformId))).thenReturn(null); + + globalDataAccessService.deleteAgent(platformId); + } + + /** + * No delete enabled when agent is connected. + */ + @Test(expectedExceptions = { ServiceException.class }) + public void testConnectedAgentDelete() throws ServiceException { + long platformId = 10L; + PlatformIdent platformIdent = new PlatformIdent(); + platformIdent.setId(platformId); + when(platformIdentDao.load(Long.valueOf(platformId))).thenReturn(platformIdent); + + Map map = new HashMap(1); + AgentStatusData agentStatusData = new AgentStatusData(AgentConnection.CONNECTED); + map.put(platformId, agentStatusData); + when(agentStatusProvider.getAgentStatusDataMap()).thenReturn(map); + + globalDataAccessService.deleteAgent(platformId); + } + + /** + * Delete enabled when Agent is not connected. + */ + @Test + public void testAgentDelete() throws ServiceException { + long platformId = 10L; + PlatformIdent platformIdent = new PlatformIdent(); + platformIdent.setId(platformId); + when(platformIdentDao.load(Long.valueOf(platformId))).thenReturn(platformIdent); + + Map map = new HashMap(1); + AgentStatusData agentStatusData = new AgentStatusData(AgentConnection.DISCONNECTED); + map.put(platformId, agentStatusData); + when(agentStatusProvider.getAgentStatusDataMap()).thenReturn(map); + + globalDataAccessService.deleteAgent(platformId); + + verify(platformIdentDao, times(1)).delete(platformIdent); + verify(defaultDataDao, times(1)).deleteAll(platformId); + verify(agentStatusProvider, times(1)).registerDeleted(platformId); + } +} diff --git a/CMR/test/info/novatec/inspectit/cmr/service/RegistrationServiceTest.java b/CMR/test/info/novatec/inspectit/cmr/service/RegistrationServiceTest.java new file mode 100644 index 000000000..50d9a8d5c --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/service/RegistrationServiceTest.java @@ -0,0 +1,610 @@ +package info.novatec.inspectit.cmr.service; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.cmr.dao.MethodIdentToSensorTypeDao; +import info.novatec.inspectit.cmr.dao.impl.MethodIdentDaoImpl; +import info.novatec.inspectit.cmr.dao.impl.MethodSensorTypeIdentDaoImpl; +import info.novatec.inspectit.cmr.dao.impl.PlatformIdentDaoImpl; +import info.novatec.inspectit.cmr.dao.impl.PlatformSensorTypeIdentDaoImpl; +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.model.MethodIdentToSensorType; +import info.novatec.inspectit.cmr.model.MethodSensorTypeIdent; +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.cmr.model.PlatformSensorTypeIdent; +import info.novatec.inspectit.cmr.service.exception.ServiceException; +import info.novatec.inspectit.cmr.test.AbstractTestNGLogSupport; +import info.novatec.inspectit.cmr.util.AgentStatusDataProvider; + +import java.rmi.RemoteException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections.MapUtils; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Thesting the {@link RegistrationService} of CMR. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("PMD") +public class RegistrationServiceTest extends AbstractTestNGLogSupport { + + /** + * Service to test. + */ + private RegistrationService registrationService; + + /** + * Mocked {@link PlatformIdentDaoImpl}. + */ + @Mock + private PlatformIdentDaoImpl platformIdentDao; + + /** + * Mocked {@link MethodIdentDaoImpl}. + */ + @Mock + private MethodIdentDaoImpl methodIdentDao; + + /** + * Mocked {@link MethodSensorTypeIdentDaoImpl}. + */ + @Mock + private MethodSensorTypeIdentDaoImpl methodSensorTypeIdentDao; + + /** + * Mocked {@link PlatformSensorTypeIdentDaoImpl}. + */ + @Mock + private PlatformSensorTypeIdentDaoImpl platformSensorTypeIdentDao; + + @Mock + private AgentStatusDataProvider agentStatusDataProvider; + + @Mock + private MethodIdentToSensorTypeDao methodIdentToSensorTypeDao; + + /** + * Initializes mocks. Has to run before each test so that mocks are clear. + */ + @BeforeMethod + public void init() { + MockitoAnnotations.initMocks(this); + + registrationService = new RegistrationService(); + registrationService.platformIdentDao = platformIdentDao; + registrationService.methodIdentDao = methodIdentDao; + registrationService.methodSensorTypeIdentDao = methodSensorTypeIdentDao; + registrationService.platformSensorTypeIdentDao = platformSensorTypeIdentDao; + registrationService.agentStatusDataProvider = agentStatusDataProvider; + registrationService.methodIdentToSensorTypeDao = methodIdentToSensorTypeDao; + registrationService.log = LoggerFactory.getLogger(RegistrationService.class); + } + + /** + * Tests that an exception will be thrown if the database returns two or more platform idents + * after findByExample search. + * + * @throws RemoteException + * If remote exception occurs. + * @throws ServiceException + */ + @Test(expectedExceptions = { ServiceException.class }) + public void noRegistrationTwoAgents() throws RemoteException, ServiceException { + List definedIps = new ArrayList(); + definedIps.add("ip"); + String agentName = "agentName"; + String version = "version"; + + List dbResponseList = new ArrayList(); + dbResponseList.add(new PlatformIdent()); + dbResponseList.add(new PlatformIdent()); + when(platformIdentDao.findByExample((PlatformIdent) anyObject())).thenReturn(dbResponseList); + + registrationService.registerPlatformIdent(definedIps, agentName, version); + } + + /** + * Test that registration will be done properly if the {@link LicenseUtil} validates license. + * + * @throws LicenseContentException + * If {@link LicenseContentException} occurs. + * @throws RemoteException + * If remote exception occurs. + * @throws ServiceException + * If {@link ServiceException} occurs. + */ + @Test + public void registerNewPlatformIdent() throws RemoteException, ServiceException { + final long platformId = 10; + List definedIps = new ArrayList(); + definedIps.add("ip"); + String agentName = "agentName"; + String version = "version"; + + when(platformIdentDao.findByExample((PlatformIdent) anyObject())).thenReturn(Collections. emptyList()); + Mockito.doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + PlatformIdent platformIdent = (PlatformIdent) invocation.getArguments()[0]; + platformIdent.setId(Long.valueOf(platformId)); + return null; + } + }).when(platformIdentDao).saveOrUpdate((PlatformIdent) anyObject()); + + long registeredId = registrationService.registerPlatformIdent(definedIps, agentName, version); + assertThat(registeredId, is(equalTo(platformId))); + + ArgumentCaptor argument = ArgumentCaptor.forClass(PlatformIdent.class); + verify(platformIdentDao, times(1)).saveOrUpdate(argument.capture()); + + assertThat(argument.getValue().getDefinedIPs(), is(equalTo(definedIps))); + assertThat(argument.getValue().getAgentName(), is(equalTo(agentName))); + assertThat(argument.getValue().getVersion(), is(equalTo(version))); + assertThat(argument.getValue().getTimeStamp(), is(notNullValue())); + + verify(agentStatusDataProvider, times(1)).registerConnected(platformId); + } + + /** + * Tests that the version and timestamp will be updated if the agent is already registered. + * + * @throws LicenseContentException + * If {@link LicenseContentException} occurs. + * @throws RemoteException + * If remote exception occurs. + * @throws ServiceException + * If {@link ServiceException} occurs. + */ + @Test + public void registerExistingPlatformIdent() throws RemoteException, ServiceException { + long platformId = 10; + List definedIps = new ArrayList(); + definedIps.add("ip"); + String agentName = "agentName"; + String version = "version"; + Timestamp timestamp = new Timestamp(1); + + PlatformIdent platformIdent = new PlatformIdent(); + platformIdent.setId(Long.valueOf(platformId)); + platformIdent.setAgentName(agentName); + platformIdent.setDefinedIPs(definedIps); + platformIdent.setVersion("versionOld"); + platformIdent.setTimeStamp(timestamp); + List findByExampleList = new ArrayList(); + findByExampleList.add(platformIdent); + + when(platformIdentDao.findByExample((PlatformIdent) anyObject())).thenReturn(findByExampleList); + + long registeredId = registrationService.registerPlatformIdent(definedIps, agentName, version); + assertThat(registeredId, is(equalTo(platformId))); + + ArgumentCaptor argument = ArgumentCaptor.forClass(PlatformIdent.class); + verify(platformIdentDao, times(1)).saveOrUpdate(argument.capture()); + + assertThat(argument.getValue().getDefinedIPs(), is(equalTo(definedIps))); + assertThat(argument.getValue().getAgentName(), is(equalTo(agentName))); + assertThat(argument.getValue().getVersion(), is(equalTo(version))); + assertThat(argument.getValue().getTimeStamp(), is(notNullValue())); + assertThat(argument.getValue().getTimeStamp(), is(not(timestamp))); + + verify(agentStatusDataProvider, times(1)).registerConnected(platformId); + } + + /** + * Test that registration will be done properlly if the {@link LicenseUtil} validates license + * and IP based registration is off. + * + * @throws LicenseContentException + * If {@link LicenseContentException} occurs. + * @throws RemoteException + * @throws ServiceException + * If {@link ServiceException} occurs. + */ + @Test + public void registerNewPlatformIdentNoIpBased() throws RemoteException, ServiceException { + final long platformId = 10; + List definedIps = new ArrayList(); + definedIps.add("ip"); + String agentName = "agentName"; + String version = "version"; + + registrationService.ipBasedAgentRegistration = false; + when(platformIdentDao.findByExample((PlatformIdent) anyObject())).thenReturn(Collections. emptyList()); + Mockito.doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + PlatformIdent platformIdent = (PlatformIdent) invocation.getArguments()[0]; + platformIdent.setId(Long.valueOf(platformId)); + return null; + } + }).when(platformIdentDao).saveOrUpdate((PlatformIdent) anyObject()); + + long registeredId = registrationService.registerPlatformIdent(definedIps, agentName, version); + assertThat(registeredId, equalTo(platformId)); + + ArgumentCaptor argument = ArgumentCaptor.forClass(PlatformIdent.class); + verify(platformIdentDao, times(1)).saveOrUpdate(argument.capture()); + + assertThat(argument.getValue().getDefinedIPs(), equalTo(definedIps)); + assertThat(argument.getValue().getAgentName(), equalTo(agentName)); + assertThat(argument.getValue().getVersion(), equalTo(version)); + assertThat(argument.getValue().getTimeStamp(), is(notNullValue())); + + verify(agentStatusDataProvider, times(1)).registerConnected(platformId); + } + + /** + * Tests that the version and timestamp will be updated if the agent is already registered and + * IP registration is off. + * + * @throws LicenseContentException + * If {@link LicenseContentException} occurs. + * @throws RemoteException + * If remote exception occurs. + * @throws ServiceException + * If {@link ServiceException} occurs. + */ + @Test + public void registerExistingPlatformIdentNoIpBased() throws RemoteException, ServiceException { + long platformId = 10; + List definedIps = new ArrayList(); + definedIps.add("ip"); + String agentName = "agentName"; + String version = "version"; + Timestamp timestamp = new Timestamp(1); + + PlatformIdent platformIdent = new PlatformIdent(); + platformIdent.setId(Long.valueOf(platformId)); + platformIdent.setAgentName(agentName); + platformIdent.setDefinedIPs(Collections. emptyList()); + platformIdent.setVersion("versionOld"); + platformIdent.setTimeStamp(timestamp); + List findByExampleList = new ArrayList(); + findByExampleList.add(platformIdent); + + registrationService.ipBasedAgentRegistration = false; + when(platformIdentDao.findByExample((PlatformIdent) anyObject())).thenReturn(findByExampleList); + + long registeredId = registrationService.registerPlatformIdent(definedIps, agentName, version); + assertThat(registeredId, equalTo(platformId)); + + ArgumentCaptor argument = ArgumentCaptor.forClass(PlatformIdent.class); + verify(platformIdentDao, times(1)).saveOrUpdate(argument.capture()); + + assertThat(argument.getValue().getDefinedIPs(), equalTo(definedIps)); + assertThat(argument.getValue().getAgentName(), equalTo(agentName)); + assertThat(argument.getValue().getVersion(), equalTo(version)); + assertThat(argument.getValue().getTimeStamp(), is(notNullValue())); + assertThat(argument.getValue().getTimeStamp(), not(equalTo(timestamp))); + + verify(agentStatusDataProvider, times(1)).registerConnected(platformId); + } + + /** + * Test unregistration of platform ident. + */ + @Test + public void unregisterPlatformIdent() throws ServiceException { + long platformId = 10; + List definedIps = new ArrayList(); + definedIps.add("ip"); + String agentName = "agentName"; + + PlatformIdent platformIdent = new PlatformIdent(); + platformIdent.setId(platformId); + platformIdent.setAgentName(agentName); + platformIdent.setDefinedIPs(definedIps); + List findByExampleList = new ArrayList(); + findByExampleList.add(platformIdent); + + when(platformIdentDao.findByExample((PlatformIdent) anyObject())).thenReturn(findByExampleList); + + registrationService.unregisterPlatformIdent(definedIps, agentName); + + verify(agentStatusDataProvider, times(1)).registerDisconnected(platformId); + } + + /** + * Confirm that {@link ServiceException} is thrown if platform ident can not be located. + */ + @Test(expectedExceptions = { ServiceException.class }) + public void unregisterNotExistingPlatformIdent() throws ServiceException { + List definedIps = new ArrayList(); + definedIps.add("ip"); + String agentName = "agentName"; + + when(platformIdentDao.findByExample((PlatformIdent) anyObject())).thenReturn(Collections. emptyList()); + + registrationService.unregisterPlatformIdent(definedIps, agentName); + } + + /** + * Tests registration of the new {@link MethodIdent}. + * + * @throws RemoteException + * If {@link RemoteException} occurs. + */ + @Test + public void registerNewMethodIdent() throws RemoteException { + final long methodId = 20; + long platformId = 1; + String packageName = "package"; + String className = "class"; + String methodName = "method"; + List parameterTypes = new ArrayList(); + parameterTypes.add("parameter"); + String returnType = "returnType"; + int modifiers = 2; + + PlatformIdent platformIdent = new PlatformIdent(); + when(platformIdentDao.load(platformId)).thenReturn(platformIdent); + when(methodIdentDao.findForPlatformIdent(eq(platformId), (MethodIdent) anyObject())).thenReturn(Collections. emptyList()); + Mockito.doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + MethodIdent methodIdent = (MethodIdent) invocation.getArguments()[0]; + methodIdent.setId(Long.valueOf(methodId)); + return null; + } + }).when(methodIdentDao).saveOrUpdate((MethodIdent) anyObject()); + + long registeredId = registrationService.registerMethodIdent(platformId, packageName, className, methodName, parameterTypes, returnType, modifiers); + assertThat(registeredId, equalTo(methodId)); + + ArgumentCaptor argument = ArgumentCaptor.forClass(MethodIdent.class); + verify(methodIdentDao, times(1)).saveOrUpdate(argument.capture()); + + assertThat(argument.getValue().getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(argument.getValue().getPackageName(), is(equalTo(packageName))); + assertThat(argument.getValue().getClassName(), is(equalTo(className))); + assertThat(argument.getValue().getMethodName(), is(equalTo(methodName))); + assertThat(argument.getValue().getParameters(), is(equalTo(parameterTypes))); + assertThat(argument.getValue().getReturnType(), is(equalTo(returnType))); + assertThat(argument.getValue().getModifiers(), is(equalTo(modifiers))); + assertThat(argument.getValue(), is(notNullValue())); + } + + /** + * Tests registration of the existing {@link MethodIdent}. + * + * @throws RemoteException + * If {@link RemoteException} occurs. + */ + @Test + public void registerExistnigMethodIdent() throws RemoteException { + final long methodId = 20; + long platformId = 1; + String packageName = "package"; + String className = "class"; + String methodName = "method"; + List parameterTypes = new ArrayList(); + parameterTypes.add("parameter"); + String returnType = "returnType"; + int modifiers = 2; + Timestamp timestamp = new Timestamp(1); + + MethodIdent methodIdent = new MethodIdent(); + methodIdent.setId(Long.valueOf(methodId)); + methodIdent.setPackageName(packageName); + methodIdent.setClassName(className); + methodIdent.setMethodName(methodName); + methodIdent.setParameters(parameterTypes); + methodIdent.setReturnType(returnType); + methodIdent.setModifiers(modifiers); + methodIdent.setTimeStamp(timestamp); + + List findByExampleList = new ArrayList(); + findByExampleList.add(methodIdent); + + PlatformIdent platformIdent = new PlatformIdent(); + methodIdent.setPlatformIdent(platformIdent); + when(platformIdentDao.load(platformId)).thenReturn(platformIdent); + when(methodIdentDao.findForPlatformIdent(eq(platformId), (MethodIdent) anyObject())).thenReturn(findByExampleList); + + long registeredId = registrationService.registerMethodIdent(platformId, packageName, className, methodName, parameterTypes, returnType, modifiers); + assertThat(registeredId, equalTo(methodId)); + + ArgumentCaptor argument = ArgumentCaptor.forClass(MethodIdent.class); + verify(methodIdentDao, times(1)).saveOrUpdate(argument.capture()); + + assertThat(argument.getValue().getPlatformIdent(), is(equalTo(platformIdent))); + assertThat(argument.getValue().getPackageName(), is(equalTo(packageName))); + assertThat(argument.getValue().getClassName(), is(equalTo(className))); + assertThat(argument.getValue().getMethodName(), is(equalTo(methodName))); + assertThat(argument.getValue().getParameters(), is(equalTo(parameterTypes))); + assertThat(argument.getValue().getReturnType(), is(equalTo(returnType))); + assertThat(argument.getValue().getModifiers(), is(equalTo(modifiers))); + assertThat(argument.getValue(), is(notNullValue())); + assertThat(argument.getValue().getTimeStamp(), is(not(timestamp))); + } + + /** + * Test the registration of the method sensor type. + * + * @throws RemoteException + * If {@link RemoteException} occurs. + */ + @Test + public void registerMethodSensorType() throws RemoteException { + final long methodSensorId = 30; + long platformId = 1; + String fqcName = "class"; + + PlatformIdent platformIdent = new PlatformIdent(); + when(platformIdentDao.load(platformId)).thenReturn(platformIdent); + when(methodSensorTypeIdentDao.findByExample(eq(platformId), (MethodSensorTypeIdent) anyObject())).thenReturn(Collections. emptyList()); + Mockito.doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + MethodSensorTypeIdent methodSensorIdent = (MethodSensorTypeIdent) invocation.getArguments()[0]; + methodSensorIdent.setId(Long.valueOf(methodSensorId)); + return null; + } + }).when(methodSensorTypeIdentDao).saveOrUpdate((MethodSensorTypeIdent) anyObject()); + + long registeredId = registrationService.registerMethodSensorTypeIdent(platformId, fqcName, Collections. emptyMap()); + assertThat(registeredId, is(equalTo(methodSensorId))); + + ArgumentCaptor methodSensorArgument = ArgumentCaptor.forClass(MethodSensorTypeIdent.class); + verify(methodSensorTypeIdentDao, times(1)).saveOrUpdate(methodSensorArgument.capture()); + assertThat(methodSensorArgument.getValue().getFullyQualifiedClassName(), is(equalTo(fqcName))); + + verify(platformIdentDao, times(1)).saveOrUpdate(platformIdent); + assertThat(methodSensorArgument.getValue(), is(equalTo(platformIdent.getSensorTypeIdents().toArray()[0]))); + } + + /** + * Test that the registration of the {@link MethodSensorTypeIdent} will be correct if properties + * are provided. + * + * @throws RemoteException + * If {@link RemoteException} occurs. + */ + @SuppressWarnings("unchecked") + @Test + public void registerMethodSensorTypeWithSettings() throws RemoteException { + final long methodSensorId = 30; + long platformId = 1; + String fqcName = "class"; + String regEx = "myRegEx"; + String regExTemplate = "myRegExTemplate"; + + Map settings = MapUtils.putAll(new HashMap(), new String[][] { { "regEx", regEx }, { "regExTemplate", regExTemplate } }); + + PlatformIdent platformIdent = new PlatformIdent(); + when(platformIdentDao.load(platformId)).thenReturn(platformIdent); + when(methodSensorTypeIdentDao.findByExample(eq(platformId), (MethodSensorTypeIdent) anyObject())).thenReturn(Collections. emptyList()); + Mockito.doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + MethodSensorTypeIdent methodSensorIdent = (MethodSensorTypeIdent) invocation.getArguments()[0]; + methodSensorIdent.setId(Long.valueOf(methodSensorId)); + return null; + } + }).when(methodSensorTypeIdentDao).saveOrUpdate((MethodSensorTypeIdent) anyObject()); + + long registeredId = registrationService.registerMethodSensorTypeIdent(platformId, fqcName, settings); + assertThat(registeredId, is(equalTo(methodSensorId))); + + ArgumentCaptor methodSensorArgument = ArgumentCaptor.forClass(MethodSensorTypeIdent.class); + verify(methodSensorTypeIdentDao, times(1)).saveOrUpdate(methodSensorArgument.capture()); + assertThat(methodSensorArgument.getValue().getSettings(), is(settings)); + } + + /** + * Test the registration of the platform sensor type. + * + * @throws RemoteException + * If {@link RemoteException} occurs. + */ + @Test + public void registerPlatformSensorType() throws RemoteException { + final long platformSensorId = 20; + long platformId = 1; + String fqcName = "class"; + + PlatformIdent platformIdent = new PlatformIdent(); + when(platformIdentDao.load(platformId)).thenReturn(platformIdent); + when(platformSensorTypeIdentDao.findByExample(eq(platformId), (PlatformSensorTypeIdent) anyObject())).thenReturn(Collections. emptyList()); + Mockito.doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + PlatformSensorTypeIdent platformSensorTypeIdent = (PlatformSensorTypeIdent) invocation.getArguments()[0]; + platformSensorTypeIdent.setId(Long.valueOf(platformSensorId)); + return null; + } + }).when(platformSensorTypeIdentDao).saveOrUpdate((PlatformSensorTypeIdent) anyObject()); + + long registeredId = registrationService.registerPlatformSensorTypeIdent(platformId, fqcName); + assertThat(registeredId, is(equalTo(platformSensorId))); + + ArgumentCaptor platformSensorArgument = ArgumentCaptor.forClass(PlatformSensorTypeIdent.class); + verify(platformSensorTypeIdentDao, times(1)).saveOrUpdate(platformSensorArgument.capture()); + assertThat(platformSensorArgument.getValue().getFullyQualifiedClassName(), is(equalTo(fqcName))); + + verify(platformIdentDao, times(1)).saveOrUpdate(platformIdent); + assertThat(platformSensorArgument.getValue(), is(equalTo(platformIdent.getSensorTypeIdents().toArray()[0]))); + } + + /** + * Test the registering of the method sensor type to method occurring for the first time. + * + * @throws RemoteException + * If {@link RemoteException} occurs. + */ + @Test + public void registerSensorTypeWithMethodFirstTime() throws RemoteException { + long methodId = 20; + long methodSensorId = 50; + + MethodIdent methodIdent = new MethodIdent(); + MethodSensorTypeIdent methodSensorTypeIdent = new MethodSensorTypeIdent(); + + when(methodIdentToSensorTypeDao.find(methodId, methodSensorId)).thenReturn(null); + when(methodIdentDao.load(methodId)).thenReturn(methodIdent); + when(methodSensorTypeIdentDao.load(methodSensorId)).thenReturn(methodSensorTypeIdent); + + registrationService.addSensorTypeToMethod(methodSensorId, methodId); + + ArgumentCaptor argument = ArgumentCaptor.forClass(MethodIdentToSensorType.class); + verify(methodIdentToSensorTypeDao, times(1)).saveOrUpdate(argument.capture()); + + assertThat(argument.getValue().getMethodIdent(), is(equalTo(methodIdent))); + assertThat(argument.getValue().getMethodSensorTypeIdent(), is(equalTo(methodSensorTypeIdent))); + } + + /** + * Test the registering of the method sensor type to method occurring not for the first time. + * + * @throws RemoteException + * If {@link RemoteException} occurs. + */ + @Test + public void registerSensorTypeWithMethodSecondTime() throws RemoteException { + long methodId = 20; + long methodSensorId = 50; + + MethodIdentToSensorType methodIdentToSensorType = new MethodIdentToSensorType(); + methodIdentToSensorType.setId(1L); + Timestamp timestamp = new Timestamp(System.currentTimeMillis() - 1); + methodIdentToSensorType.setTimestamp(timestamp); + when(methodIdentToSensorTypeDao.find(methodId, methodSensorId)).thenReturn(methodIdentToSensorType); + + registrationService.addSensorTypeToMethod(methodSensorId, methodId); + + ArgumentCaptor argument = ArgumentCaptor.forClass(MethodIdentToSensorType.class); + verify(methodIdentToSensorTypeDao, times(1)).saveOrUpdate(argument.capture()); + verifyZeroInteractions(methodIdentDao); + verifyZeroInteractions(methodSensorTypeIdentDao); + + assertThat(argument.getValue().getId(), is(equalTo(1L))); + assertThat(argument.getValue().getTimestamp().getTime(), is(greaterThan(timestamp.getTime()))); + } + +} diff --git a/CMR/test/info/novatec/inspectit/cmr/service/rest/StorageRestfulServiceTest.java b/CMR/test/info/novatec/inspectit/cmr/service/rest/StorageRestfulServiceTest.java new file mode 100644 index 000000000..447b95b5c --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/service/rest/StorageRestfulServiceTest.java @@ -0,0 +1,163 @@ +package info.novatec.inspectit.cmr.service.rest; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.cmr.service.IStorageService; +import info.novatec.inspectit.communication.data.cmr.RecordingData; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.processor.impl.InvocationExtractorDataProcessor; +import info.novatec.inspectit.storage.recording.RecordingProperties; +import info.novatec.inspectit.storage.recording.RecordingState; + +import java.util.Collections; +import java.util.Date; +import java.util.Map; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +/** + * Tests the {@link StorageRestfulService}. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("PMD") +public class StorageRestfulServiceTest { + + /** + * Class under test. + */ + private StorageRestfulService restfulService; + + /** + * Mocked {@link IStorageService}. + */ + @Mock + private IStorageService storageService; + + /** + * {@link StorageData}. + */ + @Mock + private StorageData storageData; + + /** + * Init. + */ + @BeforeTest + public void init() { + MockitoAnnotations.initMocks(this); + restfulService = new StorageRestfulService(); + restfulService.storageService = storageService; + } + + @Test + public void getStorageById() { + String id = "id"; + when(storageData.getId()).thenReturn(id); + when(storageService.getExistingStorages()).thenReturn(Collections.singletonList(storageData)); + + assertThat(restfulService.getStorageById(id), is(storageData)); + + when(storageData.getId()).thenReturn(""); + assertThat(restfulService.getStorageById(id), is(nullValue())); + } + + @Test + public void createStorage() throws StorageException { + String name = "name"; + restfulService.createStorage(name); + + ArgumentCaptor captor = ArgumentCaptor.forClass(StorageData.class); + verify(storageService, times(1)).createAndOpenStorage(captor.capture()); + assertThat(captor.getValue().getName(), is(name)); + } + + @Test(expectedExceptions = { StorageException.class }) + public void createStorageEmptyName() throws StorageException { + restfulService.createStorage(""); + } + + @Test + public void deleteStorage() throws StorageException { + String id = "id"; + restfulService.deleteStorage(id); + + ArgumentCaptor captor = ArgumentCaptor.forClass(StorageData.class); + verify(storageService, times(1)).deleteStorage(captor.capture()); + assertThat(captor.getValue().getId(), is(id)); + } + + @Test + public void finalizeStorage() throws StorageException { + String id = "id"; + restfulService.finalizeStorage(id); + + ArgumentCaptor captor = ArgumentCaptor.forClass(StorageData.class); + verify(storageService, times(1)).closeStorage(captor.capture()); + assertThat(captor.getValue().getId(), is(id)); + } + + @Test + public void recordingStatus() { + RecordingData recordingData = mock(RecordingData.class, Mockito.RETURNS_SMART_NULLS); + when(storageService.getRecordingData()).thenReturn(recordingData); + + when(storageService.getRecordingState()).thenReturn(RecordingState.OFF); + assertThat(restfulService.getRecordingState(), hasEntry("recordingState", (Object) RecordingState.OFF)); + verifyZeroInteractions(recordingData); + + when(storageService.getRecordingState()).thenReturn(RecordingState.ON); + when(recordingData.getRecordEndDate()).thenReturn(new Date()); + Map result = restfulService.getRecordingState(); + assertThat(result, hasEntry("recordingState", (Object) RecordingState.ON)); + assertThat(result, hasKey("recordingStopDate")); + + when(storageService.getRecordingState()).thenReturn(RecordingState.SCHEDULED); + when(recordingData.getRecordStartDate()).thenReturn(new Date()); + result = restfulService.getRecordingState(); + assertThat(result, hasEntry("recordingState", (Object) RecordingState.SCHEDULED)); + assertThat(result, hasKey("schduledStartDate")); + } + + @SuppressWarnings("deprecation") + @Test + public void startRecording() throws StorageException { + String id = "id"; + StorageData storageData = new StorageData(); + storageData.setId(id); + when(storageService.getExistingStorages()).thenReturn(Collections.singletonList(storageData)); + restfulService.startOrScheduleRecording(id, 10L, 20L, true, true); + + ArgumentCaptor propertiesCaptor = ArgumentCaptor.forClass(RecordingProperties.class); + verify(storageService, times(1)).startOrScheduleRecording(eq(storageData), propertiesCaptor.capture()); + + assertThat(propertiesCaptor.getValue().getStartDelay(), is(10L)); + assertThat(propertiesCaptor.getValue().getRecordDuration(), is(20L)); + assertThat(propertiesCaptor.getValue().isAutoFinalize(), is(true)); + assertThat(propertiesCaptor.getValue().getRecordingDataProcessors(), hasItem(is(InvocationExtractorDataProcessor.class))); + + restfulService.startOrScheduleRecording(id, 10L, 20L, false, false); + verify(storageService, times(2)).startOrScheduleRecording(eq(storageData), propertiesCaptor.capture()); + + assertThat(propertiesCaptor.getValue().isAutoFinalize(), is(false)); + assertThat(propertiesCaptor.getValue().getRecordingDataProcessors(), not(hasItem(is(InvocationExtractorDataProcessor.class)))); + } +} diff --git a/CMR/test/info/novatec/inspectit/cmr/storage/CmrStorageManagerTest.java b/CMR/test/info/novatec/inspectit/cmr/storage/CmrStorageManagerTest.java new file mode 100644 index 000000000..68ba3a813 --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/storage/CmrStorageManagerTest.java @@ -0,0 +1,444 @@ +package info.novatec.inspectit.cmr.storage; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.cmr.cache.IBuffer; +import info.novatec.inspectit.cmr.dao.StorageDataDao; +import info.novatec.inspectit.cmr.service.IServerStatusService; +import info.novatec.inspectit.cmr.test.AbstractTestNGLogSupport; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.cmr.WritingStatus; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.StorageManager; +import info.novatec.inspectit.storage.processor.AbstractDataProcessor; +import info.novatec.inspectit.storage.recording.RecordingProperties; +import info.novatec.inspectit.storage.recording.RecordingState; +import info.novatec.inspectit.storage.serializer.SerializationException; +import info.novatec.inspectit.storage.serializer.impl.SerializationManager; +import info.novatec.inspectit.storage.serializer.provider.SerializationManagerProvider; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.sql.Timestamp; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Test the {@link CmrStorageManager} class. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("PMD") +public class CmrStorageManagerTest extends AbstractTestNGLogSupport { + + private static final String CMR_VERSION = "v1"; + + /** + * Class under test. + */ + private CmrStorageManager storageManager; + + @Mock + private StorageDataDao storageDataDao; + + @Mock + private CmrStorageWriterProvider storageWriterProvider; + + @Mock + private CmrStorageRecorder storageRecorder; + + @Mock + private CmrStorageWriter storageWriter; + + @Mock + private SerializationManagerProvider serializationManagerProvider; + + @Mock + private SerializationManager serializer; + + @Mock + private IServerStatusService serverStatusService; + + @Mock + IBuffer buffer; + + private StorageData storageData; + + /** + * Init method. + * + * @throws Exception + */ + @BeforeMethod + public void init() throws Exception { + MockitoAnnotations.initMocks(this); + storageManager = new CmrStorageManager(); + storageManager.setStorageDefaultFolder("storageTest"); + storageManager.storageDataDao = storageDataDao; + storageManager.storageWriterProvider = storageWriterProvider; + storageManager.storageRecorder = storageRecorder; + storageManager.buffer = buffer; + storageManager.setSerializationManagerProvider(serializationManagerProvider); + storageManager.serverStatusService = serverStatusService; + storageManager.log = LoggerFactory.getLogger(CmrStorageManager.class); + when(storageWriterProvider.getCmrStorageWriter()).thenReturn(storageWriter); + when(serializationManagerProvider.createSerializer()).thenReturn(serializer); + when(serverStatusService.getVersion()).thenReturn(CMR_VERSION); + + Field field = StorageManager.class.getDeclaredField("log"); + field.setAccessible(true); + field.set(storageManager, LoggerFactory.getLogger(CmrStorageManager.class)); + + field = StorageManager.class.getDeclaredField("storageUploadsFolder"); + field.setAccessible(true); + field.set(storageManager, "uploadTest"); + + storageManager.postConstruct(); + } + + /** + * Test correct creation of storage. + */ + @Test + public void createStorage() throws IOException, SerializationException { + storageData = new StorageData(); + storageManager.createStorage(storageData); + assertThat(storageData.getId(), is(notNullValue())); + assertThat(storageData.getCmrVersion(), is(CMR_VERSION)); + assertThat(storageManager.isStorageExisting(storageData), is(true)); + assertThat(storageManager.isStorageOpen(storageData), is(false)); + assertThat(storageManager.isStorageClosed(storageData), is(false)); + assertThat(storageManager.getExistingStorages(), hasSize(1)); + assertThat(storageManager.getOpenedStorages(), is(empty())); + assertThat(storageManager.getReadableStorages(), is(empty())); + } + + /** + * Tests correct opening of storage. + */ + @Test + public void openStorage() throws IOException, SerializationException, StorageException { + storageData = new StorageData(); + storageManager.createStorage(storageData); + storageManager.openStorage(storageData); + assertThat(storageManager.isStorageExisting(storageData), is(true)); + assertThat(storageManager.isStorageOpen(storageData), is(true)); + assertThat(storageManager.isStorageClosed(storageData), is(false)); + assertThat(storageManager.getExistingStorages(), hasSize(1)); + assertThat(storageManager.getOpenedStorages(), hasSize(1)); + assertThat(storageManager.getReadableStorages(), is(empty())); + verify(storageWriterProvider, times(1)).getCmrStorageWriter(); + verify(storageWriter, times(1)).prepareForWrite(storageData); + } + + /** + * Tests that already closed storage can not be opened. + */ + @Test(expectedExceptions = { StorageException.class }) + public void canNotOpenAlreadyClosed() throws IOException, SerializationException, StorageException { + storageData = new StorageData(); + storageManager.createStorage(storageData); + storageManager.openStorage(storageData); + storageManager.closeStorage(storageData); + storageManager.openStorage(storageData); + } + + /** + * Tests closing of storage. + */ + @Test + public void closeStorage() throws IOException, SerializationException, StorageException { + storageData = new StorageData(); + storageManager.createStorage(storageData); + storageManager.openStorage(storageData); + storageManager.closeStorage(storageData); + assertThat(storageManager.isStorageExisting(storageData), is(true)); + assertThat(storageManager.isStorageOpen(storageData), is(false)); + assertThat(storageManager.isStorageClosed(storageData), is(true)); + assertThat(storageManager.getExistingStorages(), hasSize(1)); + assertThat(storageManager.getOpenedStorages(), is(empty())); + assertThat(storageManager.getReadableStorages(), hasSize(1)); + // can not verify StorageWriter.closeStorageWriter cause it s final method + } + + /** + * Tests closing of all storages. + */ + @Test + public void closeAllStorages() throws IOException, SerializationException, StorageException { + storageData = new StorageData(); + storageManager.createStorage(storageData); + storageManager.openStorage(storageData); + storageManager.closeAllStorages(); + assertThat(storageManager.isStorageExisting(storageData), is(true)); + assertThat(storageManager.isStorageOpen(storageData), is(false)); + assertThat(storageManager.isStorageClosed(storageData), is(true)); + assertThat(storageManager.getExistingStorages(), hasSize(1)); + assertThat(storageManager.getOpenedStorages(), is(empty())); + assertThat(storageManager.getReadableStorages(), hasSize(1)); + // can not verify StorageWriter.closeStorageWriter cause it s final method + } + + /** + * Tests that storage used for recording can not be closed. + */ + @Test(expectedExceptions = { StorageException.class }) + public void canNotCloseWhileRecording() throws IOException, SerializationException, StorageException { + storageData = new StorageData(); + RecordingProperties recordingProperties = mock(RecordingProperties.class); + // AbstractDataProcessor dataProcessor = mock(AbstractDataProcessor.class); + // when(recordingProperties.getRecordingDataProcessors()).thenReturn(Collections.singleton(dataProcessor)); + storageManager.startOrScheduleRecording(storageData, recordingProperties); + when(storageRecorder.isRecordingOn()).thenReturn(true); + when(storageRecorder.getRecordingState()).thenReturn(RecordingState.ON); + when(storageRecorder.getRecordingProperties()).thenReturn(recordingProperties); + when(storageRecorder.getStorageWriter()).thenReturn(storageWriter); + storageManager.closeStorage(storageData); + } + + /** + * Tests start of the recording. + */ + @Test + public void startOrScheduleRecording() throws IOException, SerializationException, StorageException { + storageData = new StorageData(); + RecordingProperties recordingProperties = mock(RecordingProperties.class); + WritingStatus writingStatus = WritingStatus.GOOD; + storageManager.startOrScheduleRecording(storageData, recordingProperties); + when(storageRecorder.isRecordingOn()).thenReturn(true); + when(storageRecorder.getRecordingState()).thenReturn(RecordingState.ON); + when(storageRecorder.getRecordingProperties()).thenReturn(recordingProperties); + when(storageRecorder.getStorageWriter()).thenReturn(storageWriter); + when(storageWriter.getWritingStatus()).thenReturn(writingStatus); + verify(storageRecorder, times(1)).startOrScheduleRecording(storageWriter, recordingProperties); + assertThat(storageManager.getRecordingState(), is(RecordingState.ON)); + assertThat(storageManager.getRecordingProperties(), is(recordingProperties)); + assertThat(storageManager.getRecordingStorage(), is(storageData)); + assertThat(storageManager.getRecordingStatus(), is(writingStatus)); + } + + /** + * Tests that recording can not be started if it s already running. + */ + @Test + public void canNotStartRecordingWhenAlreadyRunning() throws IOException, SerializationException, StorageException { + storageData = new StorageData(); + RecordingProperties recordingProperties = mock(RecordingProperties.class); + storageManager.startOrScheduleRecording(storageData, recordingProperties); + when(storageRecorder.isRecordingOn()).thenReturn(true); + when(storageRecorder.getRecordingState()).thenReturn(RecordingState.ON); + when(storageRecorder.getRecordingProperties()).thenReturn(recordingProperties); + when(storageRecorder.getStorageWriter()).thenReturn(storageWriter); + verify(storageRecorder, times(1)).startOrScheduleRecording(storageWriter, recordingProperties); + storageManager.startOrScheduleRecording(storageData, recordingProperties); + verify(storageRecorder, times(1)).startOrScheduleRecording(storageWriter, recordingProperties); + } + + /** + * Tests stop recording. + */ + @Test + public void stopRecording() throws IOException, SerializationException, StorageException { + storageData = new StorageData(); + RecordingProperties recordingProperties = mock(RecordingProperties.class); + when(recordingProperties.isAutoFinalize()).thenReturn(true); + storageManager.startOrScheduleRecording(storageData, recordingProperties); + when(storageRecorder.isRecordingOn()).thenReturn(true); + when(storageRecorder.getRecordingState()).thenReturn(RecordingState.ON); + when(storageRecorder.getRecordingProperties()).thenReturn(recordingProperties); + when(storageRecorder.getStorageWriter()).thenReturn(storageWriter); + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + when(storageRecorder.isRecordingOn()).thenReturn(false); + when(storageRecorder.getRecordingState()).thenReturn(RecordingState.OFF); + return null; + } + }).when(storageRecorder).stopRecording(); + storageManager.stopRecording(); + + verify(storageRecorder, times(1)).stopRecording(); + assertThat(storageManager.isStorageClosed(storageData), is(true)); // due to auto-finalize + assertThat(storageManager.getRecordingState(), is(RecordingState.OFF)); + } + + /** + * Tests data to be recorded. + */ + @Test + public void record() { + storageManager = spy(storageManager); + DefaultData defaultData = mock(DefaultData.class); + when(storageManager.canWriteMore()).thenReturn(true); + when(storageRecorder.isRecordingOn()).thenReturn(true); + storageManager.record(defaultData); + + verify(storageRecorder, times(1)).record(defaultData); + when(storageRecorder.isRecordingOn()).thenReturn(false); + } + + /** + * Tests that stop recording will be executed if manager reports that can not write more on + * disk. + */ + @Test + public void stopRecordingWhenCanNotWriteMore() throws IOException, SerializationException, StorageException { + storageManager = spy(storageManager); + DefaultData defaultData = mock(DefaultData.class); + when(storageManager.canWriteMore()).thenReturn(false); + when(storageRecorder.isRecordingOn()).thenReturn(true); + Mockito.doNothing().when(storageManager).stopRecording(); + storageManager.record(defaultData); + verify(storageManager, times(1)).stopRecording(); + } + + /** + * Tests writing of data to disk. + */ + @Test + public void writeDataToStorage() throws StorageException, IOException, SerializationException { + storageData = new StorageData(); + storageManager.createStorage(storageData); + storageManager.openStorage(storageData); + + Collection data = Collections.singleton(mock(DefaultData.class)); + Collection processors = Collections.singleton(mock(AbstractDataProcessor.class)); + + // first synchronously + storageManager.writeToStorage(storageData, data, processors, true); + verify(storageWriter, times(1)).processSynchronously(data, processors); + + // then asynchronously + storageManager.writeToStorage(storageData, data, processors, false); + verify(storageWriter, times(1)).process(data, processors); + } + + /** + * Proves that no writing can be done to a closed storage. + */ + @Test(expectedExceptions = { StorageException.class }) + public void canNotWriteToClosedStorage() throws StorageException, IOException, SerializationException { + storageData = new StorageData(); + storageManager.createStorage(storageData); + storageManager.openStorage(storageData); + storageManager.closeStorage(storageData); + + Collection data = Collections.singleton(mock(DefaultData.class)); + Collection processors = Collections.singleton(mock(AbstractDataProcessor.class)); + + // first synchronously + storageManager.writeToStorage(storageData, data, processors, true); + } + + /** + * Tests copy buffer action. + */ + @Test + public void copyBufferToStorage() throws IOException, SerializationException, StorageException { + storageData = new StorageData(); + + DefaultData defaultData = mock(DefaultData.class); + Timestamp timestamp = mock(Timestamp.class); + when(defaultData.getTimeStamp()).thenReturn(timestamp); + when(buffer.getOldestElement()).thenReturn(defaultData); + + List data = Collections.singletonList(mock(DefaultData.class)); + Collection processors = Collections.singleton(mock(AbstractDataProcessor.class)); + Long platformId = 10L; + List platformIdents = Collections.singletonList(platformId); + storageManager = spy(storageManager); + when(storageDataDao.getAllDefaultDataForAgent(eq(platformId), Mockito. any(), Mockito. any())).thenReturn(data); + + // first with no auto-finalize + storageManager.copyBufferToStorage(storageData, platformIdents, processors, false); + verify(storageDataDao, times(1)).getAllDefaultDataForAgent(eq(platformId), Mockito. any(), Mockito. any()); + verify(storageManager, times(1)).writeToStorage(storageData, data, processors, true); + + // first with auto-finalize + storageManager.copyBufferToStorage(storageData, platformIdents, processors, true); + verify(storageDataDao, times(2)).getAllDefaultDataForAgent(eq(platformId), Mockito. any(), Mockito. any()); + verify(storageManager, times(2)).writeToStorage(storageData, data, processors, true); + assertThat(storageManager.isStorageClosed(storageData), is(true)); + } + + /** + * Tests copy data to storage action. + */ + @SuppressWarnings("unchecked") + public void copyDataToStorage() throws IOException, SerializationException, StorageException { + storageData = new StorageData(); + + List data = Collections.singletonList(mock(DefaultData.class)); + Collection processors = Collections.singleton(mock(AbstractDataProcessor.class)); + storageManager = spy(storageManager); + long platformIdent = 10L; + Collection elementIds = mock(Collection.class); + when(storageDataDao.getDataFromIdList(elementIds, platformIdent)).thenReturn(data); + + // first with no auto-finalize + storageManager.copyDataToStorage(storageData, elementIds, platformIdent, processors, false); + verify(storageDataDao, times(1)).getDataFromIdList(elementIds, platformIdent); + verify(storageManager, times(1)).writeToStorage(storageData, data, processors, true); + + // first with auto-finalize + storageManager.copyDataToStorage(storageData, elementIds, platformIdent, processors, false); + verify(storageDataDao, times(2)).getDataFromIdList(elementIds, platformIdent); + verify(storageManager, times(2)).writeToStorage(storageData, data, processors, true); + assertThat(storageManager.isStorageClosed(storageData), is(true)); + } + + /** + * After processing to delete storage that might be created in the test. + */ + @AfterMethod + public void deleteStorage() throws StorageException, IOException, SerializationException { + if (null != storageData) { + if (storageManager.getRecordingState() == RecordingState.ON) { + storageManager.stopRecording(); + } + if (!storageManager.isStorageClosed(storageData)) { + storageManager.closeStorage(storageData); + } + storageManager.deleteStorage(storageData); + storageData = null; + } + assertThat(storageManager.getExistingStorages(), is(empty())); + } + + /** + * After tests delete created folders. + */ + @AfterTest + public void deleteFolders() throws IOException { + Files.deleteIfExists(Paths.get(storageManager.getStorageDefaultFolder())); + Files.deleteIfExists(Paths.get(storageManager.getStorageUploadsFolder())); + } + +} diff --git a/CMR/test/info/novatec/inspectit/cmr/storage/CmrStorageRecorderTest.java b/CMR/test/info/novatec/inspectit/cmr/storage/CmrStorageRecorderTest.java new file mode 100644 index 000000000..b4a62a03c --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/storage/CmrStorageRecorderTest.java @@ -0,0 +1,181 @@ +package info.novatec.inspectit.cmr.storage; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.cmr.dao.StorageDataDao; +import info.novatec.inspectit.cmr.test.AbstractTestNGLogSupport; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.SystemInformationData; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.StorageWriter; +import info.novatec.inspectit.storage.processor.AbstractDataProcessor; +import info.novatec.inspectit.storage.recording.RecordingProperties; +import info.novatec.inspectit.storage.serializer.SerializationException; + +import java.io.IOException; +import java.util.Collections; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Test for the {@link CmrStorageRecorder}. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("PMD") +public class CmrStorageRecorderTest extends AbstractTestNGLogSupport { + + /** + * Class under test. + */ + private CmrStorageRecorder cmrStorageRecorder; + + @Mock + private CmrStorageManager cmrStorageManager; + + @Mock + private StorageDataDao storageDataDao; + + @Mock + private ScheduledExecutorService executorService; + + @Mock + private AbstractDataProcessor dataProcessor; + + @Mock + private StorageWriter storageWriter; + + @Mock + private RecordingProperties recordingProperties; + + /** + * Init method. + */ + @BeforeMethod + public void init() { + MockitoAnnotations.initMocks(this); + cmrStorageRecorder = new CmrStorageRecorder(); + cmrStorageRecorder.storageDataDao = storageDataDao; + cmrStorageRecorder.cmrStorageManager = cmrStorageManager; + cmrStorageRecorder.executorService = executorService; + cmrStorageRecorder.log = LoggerFactory.getLogger(CmrStorageRecorder.class); + when(storageWriter.isWritingOn()).thenReturn(true); + } + + /** + * Test that start of recording and processing of data is correct. + */ + @Test + public void processing() throws StorageException { + when(recordingProperties.getRecordingDataProcessors()).thenReturn(Collections.singleton(dataProcessor)); + cmrStorageRecorder.startOrScheduleRecording(storageWriter, recordingProperties); + assertThat(cmrStorageRecorder.isRecordingOn(), is(true)); + + DefaultData defaultData = mock(DefaultData.class); + cmrStorageRecorder.record(defaultData); + + verify(dataProcessor, times(1)).setStorageWriter(storageWriter); + verify(dataProcessor, times(1)).process(defaultData); + verifyNoMoreInteractions(dataProcessor); + } + + /** + * Tests that no data will be processed if recording is off and storage writer is turned off. + */ + @Test + public void noProcessing() throws StorageException { + cmrStorageRecorder = spy(cmrStorageRecorder); + when(recordingProperties.getRecordingDataProcessors()).thenReturn(Collections.singleton(dataProcessor)); + cmrStorageRecorder.startOrScheduleRecording(storageWriter, recordingProperties); + + DefaultData defaultData = mock(DefaultData.class); + when(cmrStorageRecorder.isRecordingOn()).thenReturn(false); + cmrStorageRecorder.record(defaultData); + when(cmrStorageRecorder.isRecordingOn()).thenReturn(true); + when(storageWriter.isWritingOn()).thenReturn(false); + cmrStorageRecorder.record(defaultData); + + verify(dataProcessor, times(0)).process(defaultData); + } + + /** + * Tests that the recording will be scheduled if start delay is specified. + */ + @Test + public void scheduleRecording() throws StorageException { + cmrStorageRecorder = spy(cmrStorageRecorder); + long recordingDelay = 1000L; + when(recordingProperties.getRecordingDataProcessors()).thenReturn(Collections.singleton(dataProcessor)); + when(recordingProperties.getStartDelay()).thenReturn(recordingDelay); + cmrStorageRecorder.startOrScheduleRecording(storageWriter, recordingProperties); + + assertThat(cmrStorageRecorder.isRecordingOn(), is(false)); + assertThat(cmrStorageRecorder.isRecordingScheduled(), is(true)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); + verify(executorService, times(1)).schedule(captor.capture(), eq(recordingDelay), eq(TimeUnit.MILLISECONDS)); + } + + /** + * Test that recording will be stopped if recording duration is set. + */ + @Test + public void scheduleRecordingStop() throws StorageException, IOException, SerializationException { + cmrStorageRecorder = spy(cmrStorageRecorder); + long recordingDuration = 1000L; + when(recordingProperties.getRecordingDataProcessors()).thenReturn(Collections.singleton(dataProcessor)); + when(recordingProperties.getRecordDuration()).thenReturn(recordingDuration); + cmrStorageRecorder.startOrScheduleRecording(storageWriter, recordingProperties); + + assertThat(cmrStorageRecorder.isRecordingOn(), is(true)); + assertThat(cmrStorageRecorder.isRecordingScheduled(), is(false)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); + verify(executorService, times(1)).schedule(captor.capture(), eq(recordingDuration), eq(TimeUnit.MILLISECONDS)); + + captor.getValue().run(); + verify(cmrStorageManager, times(1)).stopRecording(); + } + + /** + * Tests that stop recording will correctly flush the data processor and record the system + * information data for all recorded platforms. + */ + @Test + public void stopRecording() throws StorageException { + cmrStorageRecorder = spy(cmrStorageRecorder); + when(recordingProperties.getRecordingDataProcessors()).thenReturn(Collections.singleton(dataProcessor)); + cmrStorageRecorder.startOrScheduleRecording(storageWriter, recordingProperties); + + long platformId = 10L; + DefaultData defaultData = mock(DefaultData.class); + when(defaultData.getPlatformIdent()).thenReturn(platformId); + cmrStorageRecorder.record(defaultData); + + SystemInformationData systemInformationData = mock(SystemInformationData.class); + when(storageDataDao.getSystemInformationData(Collections.singleton(platformId))).thenReturn(Collections.singletonList(systemInformationData)); + + cmrStorageRecorder.stopRecording(); + + verify(dataProcessor, times(1)).flush(); + verify(cmrStorageRecorder, times(1)).record(systemInformationData); + + assertThat(cmrStorageRecorder.isRecordingOn(), is(false)); + } + +} diff --git a/CMR/test/info/novatec/inspectit/cmr/test/AbstractTestNGLogSupport.java b/CMR/test/info/novatec/inspectit/cmr/test/AbstractTestNGLogSupport.java new file mode 100644 index 000000000..70413a38c --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/test/AbstractTestNGLogSupport.java @@ -0,0 +1,49 @@ +package info.novatec.inspectit.cmr.test; + +import info.novatec.inspectit.cmr.CMR; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +import org.slf4j.LoggerFactory; +import org.testng.annotations.BeforeSuite; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.joran.spi.JoranException; +import ch.qos.logback.core.util.StatusPrinter; + +/** + * This abstract class provides general logging support for the test classes that need normal spring + * context. + * + * @author Ivan Senic + * + */ +public abstract class AbstractTestNGLogSupport { + + /** + * Init logging. + */ + @BeforeSuite + public void initLogging() throws IOException { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + context.reset(); + + Path logPath = Paths.get(CMR.DEFAULT_LOG_FILE_NAME).toAbsolutePath(); + try (InputStream is = Files.newInputStream(logPath, StandardOpenOption.READ)) { + + configurator.doConfigure(is); + } catch (JoranException je) { // NOPMD StatusPrinter will handle this + } + StatusPrinter.printInCaseOfErrorsOrWarnings(context); + } + +} diff --git a/CMR/test/info/novatec/inspectit/cmr/test/AbstractTransactionalTestNGLogSupport.java b/CMR/test/info/novatec/inspectit/cmr/test/AbstractTransactionalTestNGLogSupport.java new file mode 100644 index 000000000..b05582886 --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/test/AbstractTransactionalTestNGLogSupport.java @@ -0,0 +1,50 @@ +package info.novatec.inspectit.cmr.test; + +import info.novatec.inspectit.cmr.CMR; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +import org.slf4j.LoggerFactory; +import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests; +import org.testng.annotations.BeforeSuite; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.joran.spi.JoranException; +import ch.qos.logback.core.util.StatusPrinter; + +/** + * This abstract class provides general logging support for the test classes that need transactional + * spring context. + * + * @author Eduard Tudenhoefner + * + */ +public abstract class AbstractTransactionalTestNGLogSupport extends AbstractTransactionalTestNGSpringContextTests { + + /** + * Init logging. + */ + @BeforeSuite + public void initLogging() throws IOException { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + context.reset(); + + Path logPath = Paths.get(CMR.DEFAULT_LOG_FILE_NAME).toAbsolutePath(); + try (InputStream is = Files.newInputStream(logPath, StandardOpenOption.READ)) { + + configurator.doConfigure(is); + } catch (JoranException je) { // NOPMD StatusPrinter will handle this + } + StatusPrinter.printInCaseOfErrorsOrWarnings(context); + } + +} diff --git a/CMR/test/info/novatec/inspectit/cmr/util/AgentStatusDataProviderTest.java b/CMR/test/info/novatec/inspectit/cmr/util/AgentStatusDataProviderTest.java new file mode 100644 index 000000000..5109d98ce --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/util/AgentStatusDataProviderTest.java @@ -0,0 +1,77 @@ +package info.novatec.inspectit.cmr.util; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import info.novatec.inspectit.communication.data.cmr.AgentStatusData; +import info.novatec.inspectit.communication.data.cmr.AgentStatusData.AgentConnection; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Tests the {@link AgentStatusDataProvider}. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("PMD") +public class AgentStatusDataProviderTest { + + /** + * Class under test. + */ + private AgentStatusDataProvider agentStatusDataProvider; + + /** + * Init method. + */ + @BeforeMethod + public void init() { + agentStatusDataProvider = new AgentStatusDataProvider(); + } + + /** + * Test the correct change of statuses. + */ + @Test + public void statuses() { + long platformIdent = 10L; + agentStatusDataProvider.registerConnected(platformIdent); + assertThat(agentStatusDataProvider.getAgentStatusDataMap().size(), is(1)); + + AgentStatusData agentStatusData = agentStatusDataProvider.getAgentStatusDataMap().get(platformIdent); + assertThat(agentStatusData, is(notNullValue())); + assertThat(agentStatusData.getAgentConnection(), is(AgentConnection.CONNECTED)); + assertThat(agentStatusData.getMillisSinceLastData(), is(nullValue())); + + long millis = System.currentTimeMillis(); + agentStatusDataProvider.registerDataSent(platformIdent); + assertThat(agentStatusDataProvider.getAgentStatusDataMap().size(), is(1)); + + agentStatusData = agentStatusDataProvider.getAgentStatusDataMap().get(platformIdent); + assertThat(agentStatusData, is(notNullValue())); + assertThat(agentStatusData.getAgentConnection(), is(AgentConnection.CONNECTED)); + assertThat(agentStatusData.getMillisSinceLastData(), is(lessThanOrEqualTo(System.currentTimeMillis() - millis))); + + agentStatusDataProvider.registerDisconnected(platformIdent); + assertThat(agentStatusDataProvider.getAgentStatusDataMap().size(), is(1)); + + agentStatusData = agentStatusDataProvider.getAgentStatusDataMap().get(platformIdent); + assertThat(agentStatusData, is(notNullValue())); + assertThat(agentStatusData.getAgentConnection(), is(AgentConnection.DISCONNECTED)); + + agentStatusDataProvider.registerDeleted(platformIdent); + assertThat(agentStatusDataProvider.getAgentStatusDataMap().size(), is(0)); + } + + /** + * Test that initially there is not information. + */ + @Test + public void noStatusAvailable() { + assertThat(agentStatusDataProvider.getAgentStatusDataMap().size(), is(0)); + } +} diff --git a/CMR/test/info/novatec/inspectit/cmr/util/PlatformIdentCacheTest.java b/CMR/test/info/novatec/inspectit/cmr/util/PlatformIdentCacheTest.java new file mode 100644 index 000000000..855c452b0 --- /dev/null +++ b/CMR/test/info/novatec/inspectit/cmr/util/PlatformIdentCacheTest.java @@ -0,0 +1,61 @@ +package info.novatec.inspectit.cmr.util; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.cmr.test.AbstractTestNGLogSupport; + +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +/** + * Small test for {@link PlatformIdentCache}. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("PMD") +public class PlatformIdentCacheTest extends AbstractTestNGLogSupport { + + private PlatformIdentCache platformIdentCache; + + @Mock + private PlatformIdent platformIdent; + + @BeforeClass + public void init() { + MockitoAnnotations.initMocks(this); + platformIdentCache = new PlatformIdentCache(); + } + + /** + * Tests the simple set of actions on the {@link PlatformIdentCache}. + */ + @Test + public void cache() { + Mockito.when(platformIdent.getId()).thenReturn(-1L); + int initialSize = platformIdentCache.getSize(); + + platformIdentCache.markClean(platformIdent); + assertThat(platformIdentCache.getSize(), is(equalTo(initialSize + 1))); + assertThat(platformIdentCache.getCleanPlatformIdents(), contains(platformIdent)); + assertThat(platformIdentCache.getDirtyPlatformIdents(), is(empty())); + + platformIdentCache.markClean(platformIdent); + assertThat(platformIdentCache.getSize(), is(equalTo(initialSize + 1))); + + platformIdentCache.markDirty(platformIdent); + assertThat(platformIdentCache.getSize(), is(equalTo(initialSize + 1))); + assertThat(platformIdentCache.getDirtyPlatformIdents(), contains(platformIdent)); + + platformIdentCache.remove(platformIdent); + assertThat(platformIdentCache.getSize(), is(equalTo(initialSize))); + } + +} diff --git a/CMR/test/info/novatec/inspectit/storage/StorageIntegrationTest.java b/CMR/test/info/novatec/inspectit/storage/StorageIntegrationTest.java new file mode 100644 index 000000000..7a13b5311 --- /dev/null +++ b/CMR/test/info/novatec/inspectit/storage/StorageIntegrationTest.java @@ -0,0 +1,484 @@ +package info.novatec.inspectit.storage; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import info.novatec.inspectit.cmr.storage.CmrStorageManager; +import info.novatec.inspectit.cmr.test.AbstractTransactionalTestNGLogSupport; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.indexing.storage.IStorageDescriptor; +import info.novatec.inspectit.indexing.storage.IStorageTreeComponent; +import info.novatec.inspectit.indexing.storage.impl.StorageIndexQuery; +import info.novatec.inspectit.storage.label.StringStorageLabel; +import info.novatec.inspectit.storage.label.type.impl.RatingLabelType; +import info.novatec.inspectit.storage.nio.stream.InputStreamProvider; +import info.novatec.inspectit.storage.processor.AbstractDataProcessor; +import info.novatec.inspectit.storage.processor.impl.DataSaverProcessor; +import info.novatec.inspectit.storage.serializer.ISerializer; +import info.novatec.inspectit.storage.serializer.SerializationException; +import info.novatec.inspectit.storage.serializer.util.KryoUtil; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import com.esotericsoftware.kryo.io.Input; + +/** + * Tests the complete CMR storage functionality. + * + * @author Ivan Senic + * + */ +@ContextConfiguration(locations = { "classpath:spring/spring-context-global.xml", "classpath:spring/spring-context-database.xml", "classpath:spring/spring-context-beans.xml", + "classpath:spring/spring-context-processors.xml", "classpath:spring/spring-context-storage-test.xml" }) +@SuppressWarnings("PMD") +public class StorageIntegrationTest extends AbstractTransactionalTestNGLogSupport { + + /** + * {@link StorageManager}. + */ + @Autowired + private CmrStorageManager storageManager; + + /** + * {@link InputStreamProvider}. + */ + @Autowired + InputStreamProvider inputStreamProvider; + + /** + * {@link ISerializer}. + */ + @Autowired + private ISerializer serializer; + + /** + * Storage data to be used in testing. + */ + private StorageData storageData; + + /** + * List of invocations that will be written to storage and then read. + */ + private List createdInvocations; + + /** + * Indexing tree of storage. + */ + private IStorageTreeComponent storageIndexingTree; + + /** + * Data saver processor. + */ + private DataSaverProcessor dataSaverProcessor; + + /** + * Init. + */ + @BeforeClass + public void createStorageData() { + storageData = getStorageData(); + createdInvocations = new ArrayList(); + List> saverClasses = new ArrayList>(); + saverClasses.add(InvocationSequenceData.class); + dataSaverProcessor = new DataSaverProcessor(saverClasses, true); + } + + /** + * We can not open not-existing storage. + */ + @Test(expectedExceptions = StorageException.class) + public void openUnexisting() throws IOException, SerializationException, StorageException { + storageManager.openStorage(new StorageData()); + } + + /** + * Tests creation of storage. + * + * @throws SerializationException + * If serialization fails. + * @throws IOException + * If {@link IOException} occurs. + * @throws StorageException + */ + @Test + public void createStorageTest() throws IOException, SerializationException, StorageException { + storageManager.createStorage(storageData); + + File storageDir = getStorageFolder(); + + assertThat(storageDir.isDirectory(), is(true)); + File[] storageFiles = storageDir.listFiles(new FilenameFilter() { + + @Override + public boolean accept(File dir, String name) { + return name.endsWith(StorageFileType.STORAGE_FILE.getExtension()); + } + }); + + // only one storage file created + assertThat(storageFiles.length, is(equalTo(1))); + + // get the data from file and check for equal + byte[] storageDataBytes = Files.readAllBytes(storageFiles[0].toPath()); + Input input = new Input(storageDataBytes); + StorageData deserializedStorageData = (StorageData) serializer.deserialize(input); + assertThat(deserializedStorageData, is(equalTo(storageData))); + + storageManager.openStorage(storageData); + assertThat(storageData.isStorageOpened(), is(true)); + assertThat(storageData.isStorageClosed(), is(false)); + + // get the data from file and check for equal + storageDataBytes = Files.readAllBytes(storageFiles[0].toPath()); + input.setBuffer(storageDataBytes); + deserializedStorageData = (StorageData) serializer.deserialize(input); + assertThat(deserializedStorageData, is(equalTo(storageData))); + + // storage manager know for the storage + assertThat(storageManager.getExistingStorages(), hasItem(storageData)); + assertThat(storageManager.getOpenedStorages(), hasItem(storageData)); + assertThat(storageManager.getReadableStorages(), not(hasItem(storageData))); + assertThat(storageData.isStorageOpened(), is(true)); + assertThat(storageData.isStorageClosed(), is(false)); + } + + /** + * Test write to storage. + * + * @throws StorageException + * If {@link StorageException} occurs. + * @throws SerializationException + * If serialization fails. + * @throws IOException + * If {@link IOException} occurs. + */ + @Test(dependsOnMethods = { "createStorageTest" }) + public void testWrite() throws StorageException, IOException, SerializationException { + Random random = new Random(); + int repeat = random.nextInt(100); + List processors = new ArrayList(); + processors.add(dataSaverProcessor); + for (int i = 0; i < repeat; i++) { + InvocationSequenceData invoc = getInvocationSequenceDataInstance(1 + random.nextInt(1000)); + boolean canAdd = true; + if (invoc.getId() == 0) { + canAdd = false; + } else { + for (InvocationSequenceData inCollection : createdInvocations) { + if (invoc.getId() == inCollection.getId()) { + canAdd = false; + break; + } + } + } + if (canAdd) { + createdInvocations.add(invoc); + } + } + storageManager.writeToStorage(storageData, createdInvocations, processors, false); + } + + /** + * Test storage finalization. + * + * @throws SerializationException + * If serialization fails. + * @throws IOException + * If {@link IOException} occurs. + * @throws StorageException + * If {@link StorageException} occurs. + */ + @Test(dependsOnMethods = { "testWrite" }) + public void finalizeWriteTest() throws IOException, SerializationException, StorageException { + storageManager.closeStorage(storageData); + + assertThat(storageData.isStorageOpened(), is(false)); + assertThat(storageData.isStorageClosed(), is(true)); + + File storageFolder = getStorageFolder(); + + File[] indexFiles = storageFolder.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(StorageFileType.INDEX_FILE.getExtension()); + } + }); + assertThat(indexFiles.length, is(equalTo(1))); + + String indexFilePath = indexFiles[0].getPath(); + byte[] indexTreeBytes = Files.readAllBytes(Paths.get(indexFilePath)); + assertThat(indexTreeBytes.length, is(greaterThan(0))); + + ByteArrayInputStream bais = new ByteArrayInputStream(indexTreeBytes); + Input input = new Input(bais); + Object indexingTree = serializer.deserialize(input); + assertThat(indexingTree, is(instanceOf(IStorageTreeComponent.class))); + + storageIndexingTree = (IStorageTreeComponent) indexingTree; + + assertThat(storageManager.getReadableStorages(), hasItem(storageData)); + } + + @SuppressWarnings("unchecked") + @Test(dependsOnMethods = { "testWrite" }) + public void storageDataCaching() throws IOException, SerializationException { + int myHash = 13; + storageManager.cacheStorageData(storageData, createdInvocations, myHash); + + Path cachedFile = storageManager.getCachedDataPath(storageData, myHash); + assertThat(Files.exists(cachedFile), is(true)); + + try (InputStream inputStream = Files.newInputStream(cachedFile, StandardOpenOption.READ)) { + Input input = new Input(inputStream); + List deserialized = (List) serializer.deserialize(input); + assertThat(deserialized, is(equalTo(createdInvocations))); + } + + String pathHttp = storageManager.getCachedStorageDataFileLocation(storageData, myHash); + assertThat(pathHttp, is(notNullValue())); + + Files.deleteIfExists(cachedFile); + + pathHttp = storageManager.getCachedStorageDataFileLocation(storageData, myHash); + assertThat(pathHttp, is(nullValue())); + } + + /** + * Test that the storage can not be opened after it has been finalized. + */ + @Test(dependsOnMethods = { "finalizeWriteTest" }, expectedExceptions = { StorageException.class }) + public void canNotOpenClosed() throws IOException, SerializationException, StorageException { + storageManager.openStorage(storageData); + } + + /** + * Tests reading of data from created storage. + * + * @throws SerializationException + * If serialization fails. + * @throws IOException + * If {@link IOException} occurs. + */ + @Test(dependsOnMethods = { "finalizeWriteTest" }) + public void readTest() throws SerializationException, IOException { + if (storageIndexingTree == null) { + return; + } + + StorageIndexQuery query = new StorageIndexQuery(); + List> searchedClasses = new ArrayList>(); + searchedClasses.add(InvocationSequenceData.class); + query.setObjectClasses(searchedClasses); + + List descriptors = storageIndexingTree.query(query); + assertThat("Amount of descriptors is less than the amount of invocations saved.", descriptors.size(), is(equalTo(createdInvocations.size()))); + for (IStorageDescriptor descriptor : descriptors) { + assertThat("position of descriptor is negative.", descriptor.getPosition(), is(greaterThanOrEqualTo(0L))); + assertThat("Size of the descriptor is wrong.", descriptor.getSize(), is(greaterThan(0L))); + } + + InputStream result = inputStreamProvider.getExtendedByteBufferInputStream(storageData, descriptors); + Input input = new Input(result); + int count = 0; + try { + while (KryoUtil.hasMoreBytes(input)) { + try { + Object invocation = serializer.deserialize(input); + assertThat(invocation, is(instanceOf(InvocationSequenceData.class))); + assertThat(createdInvocations, hasItem((InvocationSequenceData) invocation)); + count++; + } catch (SerializationException e) { + e.printStackTrace(); + } + } + } finally { + result.close(); + } + assertThat("Amount of de-serialize objects is less than the amount of invocations saved.", count, is(equalTo(createdInvocations.size()))); + } + + /** + * Test adding/removing of labels to a {@link StorageData} and sucessful saving to the disk. + * + * @throws SerializationException + * If serialization fails. + * @throws IOException + * If {@link IOException} occurs. + * @throws StorageException + */ + @Test + public void testStorageLabels() throws IOException, SerializationException, StorageException { + RatingLabelType ratingLabelType = new RatingLabelType(); + StringStorageLabel label = new StringStorageLabel(); + label.setStorageLabelType(ratingLabelType); + label.setStringValue("Rating"); + + // test add + storageManager.addLabelToStorage(storageData, label, true); + for (StorageData storageToTest : storageManager.getExistingStorages()) { + if (storageToTest.getId().equals(storageData.getId())) { + assertThat(storageToTest.isLabelPresent(ratingLabelType), is(true)); + assertThat(storageToTest.getLabels(ratingLabelType).size(), is(equalTo(1))); + assertThat((StringStorageLabel) storageToTest.getLabels(ratingLabelType).get(0), is(equalTo(label))); + } + } + + // test overwrite + label = new StringStorageLabel(); + label.setStorageLabelType(ratingLabelType); + label.setStringValue("Rating1"); + storageManager.addLabelToStorage(storageData, label, true); + for (StorageData storageToTest : storageManager.getExistingStorages()) { + if (storageToTest.getId().equals(storageData.getId())) { + assertThat(storageToTest.isLabelPresent(ratingLabelType), is(true)); + assertThat(storageToTest.getLabels(ratingLabelType).size(), is(equalTo(1))); + assertThat((StringStorageLabel) storageToTest.getLabels(ratingLabelType).get(0), is(equalTo(label))); + } + } + + // test no overwrite + label = new StringStorageLabel(); + label.setStorageLabelType(ratingLabelType); + label.setStringValue("Rating2"); + storageManager.addLabelToStorage(storageData, label, false); + for (StorageData storageToTest : storageManager.getExistingStorages()) { + if (storageToTest.getId().equals(storageData.getId())) { + assertThat(storageToTest.isLabelPresent(ratingLabelType), is(true)); + assertThat(storageToTest.getLabels(ratingLabelType).size(), is(equalTo(1))); + assertThat((StringStorageLabel) storageToTest.getLabels(ratingLabelType).get(0), is(not(equalTo(label)))); + } + } + + // test remove + label = new StringStorageLabel(); + label.setStorageLabelType(ratingLabelType); + label.setStringValue("Rating1"); + assertThat(storageManager.removeLabelFromStorage(storageData, label), is(true)); + for (StorageData storageToTest : storageManager.getExistingStorages()) { + if (storageToTest.getId().equals(storageData.getId())) { + assertThat(storageToTest.isLabelPresent(ratingLabelType), is(false)); + assertThat(storageToTest.getLabels(ratingLabelType), is(empty())); + } + } + } + + /** + * Deletes created files after the test. + */ + @AfterTest + public void deleteResources() { + File storageFolder = getStorageFolder(); + if (storageFolder.exists()) { + File[] files = storageFolder.listFiles(); + for (File file : files) { + assertThat("Can not delete storage test file.", file.delete(), is(true)); + } + assertThat("Can not delete storage test folder.", storageFolder.delete(), is(true)); + } + } + + /** + * Returns storage folder. + * + * @return Returns storage folder. + */ + private File getStorageFolder() { + return new File(storageManager.getStorageDefaultFolder() + File.separator + storageData.getStorageFolder() + File.separator); + } + + /** + * @return Returns random storage data instance. + */ + private static StorageData getStorageData() { + StorageData storageData = new StorageData(); + storageData.setName("My storage"); + return storageData; + } + + /** + * + * @return One {@link SqlStatementData} with random values. + */ + private static SqlStatementData getSqlStatementInstance() { + Random random = new Random(); + SqlStatementData sqlData = new SqlStatementData(new Timestamp(random.nextLong()), random.nextLong(), random.nextLong(), random.nextLong(), "New Sql String"); + sqlData.setCount(random.nextLong()); + sqlData.setCpuDuration(random.nextDouble()); + sqlData.calculateCpuMax(random.nextDouble()); + sqlData.calculateCpuMin(random.nextDouble()); + sqlData.setDuration(random.nextDouble()); + sqlData.setExclusiveCount(random.nextLong()); + sqlData.setExclusiveDuration(random.nextDouble()); + sqlData.calculateExclusiveMax(random.nextDouble()); + sqlData.calculateExclusiveMin(random.nextDouble()); + sqlData.setId(random.nextLong()); + sqlData.addInvocationParentId(random.nextLong()); + sqlData.setPreparedStatement(true); + return sqlData; + } + + /** + * Returns the random {@link InvocationSequenceData} instance. + * + * @param childCount + * Desired child count. + * @return {@link InvocationSequenceData} instance. + */ + private static InvocationSequenceData getInvocationSequenceDataInstance(int childCount) { + Random random = new Random(); + InvocationSequenceData invData = new InvocationSequenceData(new Timestamp(random.nextLong()), random.nextLong(), random.nextLong(), random.nextLong()); + invData.setDuration(random.nextDouble()); + invData.setId(random.nextLong()); + invData.setEnd(random.nextDouble()); + invData.setSqlStatementData(getSqlStatementInstance()); + if (childCount == 0) { + return invData; + } + + List children = new ArrayList(); + for (int i = 0; i < childCount;) { + int childCountForChild = childCount / 10; + if (childCountForChild + i + 1 > childCount) { + childCountForChild = childCount - i - 1; + } + InvocationSequenceData child = getInvocationSequenceDataInstance(childCountForChild); + child.setSqlStatementData(getSqlStatementInstance()); + child.setParentSequence(invData); + children.add(child); + i += childCountForChild + 1; + + } + invData.setChildCount(childCount); + invData.setNestedSequences(children); + return invData; + } + +} diff --git a/CMR/test/info/novatec/inspectit/storage/serializer/impl/HibernateSerializerTest.java b/CMR/test/info/novatec/inspectit/storage/serializer/impl/HibernateSerializerTest.java new file mode 100644 index 000000000..18da61073 --- /dev/null +++ b/CMR/test/info/novatec/inspectit/storage/serializer/impl/HibernateSerializerTest.java @@ -0,0 +1,142 @@ +package info.novatec.inspectit.storage.serializer.impl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import info.novatec.inspectit.cmr.util.HibernateUtil; +import info.novatec.inspectit.storage.serializer.ISerializer; +import info.novatec.inspectit.storage.serializer.SerializationException; +import info.novatec.inspectit.storage.serializer.schema.ClassSchemaManager; +import info.novatec.inspectit.storage.serializer.schema.SchemaManagerTestProvider; +import info.novatec.inspectit.util.KryoNetNetwork; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.hibernate.collection.PersistentList; +import org.hibernate.collection.PersistentMap; +import org.hibernate.collection.PersistentSet; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.esotericsoftware.kryo.io.ByteBufferInputStream; +import com.esotericsoftware.kryo.io.ByteBufferOutputStream; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +/** + * Test the implementation of the {@link ISerializer} for correctness. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("PMD") +public class HibernateSerializerTest { + + /** + * Serializer. + */ + private SerializationManager serializer; + + /** + * Byte buffer. + */ + private ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024 * 1024 * 20); + + /** + * Instantiates the {@link SerializationManager}. + * + * @throws IOException + * If {@link IOException} occurs. + */ + @BeforeClass + public void initSerializer() throws IOException { + ClassSchemaManager schemaManager = SchemaManagerTestProvider.getClassSchemaManagerForTests(); + serializer = new SerializationManager(); + serializer.hibernateUtil = new HibernateUtil(); + serializer.setSchemaManager(schemaManager); + serializer.setKryoNetNetwork(new KryoNetNetwork()); + serializer.initKryo(); + } + + /** + * Prepare the buffer before the test. + */ + @BeforeMethod + public void prepareBuffer() { + byteBuffer.clear(); + } + + /** + * Tests that the Hibernate {@link PersistentList} can be serialized, but in way that + * deserialized class will be java list and but not {@link PersistentList}. + * + * @throws SerializationException + * SerializationException + */ + @Test + public void hibernatePersistentList() throws SerializationException { + PersistentList object = new PersistentList(); + Object deserialized = serializeBackAndForth(object); + assertThat(deserialized, is(not(instanceOf(PersistentList.class)))); + assertThat(deserialized, is(instanceOf(List.class))); + } + + /** + * Tests that the Hibernate {@link PersistentSet} can be serialized, but in way that + * deserialized class will be java set and but not {@link PersistentSet}. + * + * @throws SerializationException + * SerializationException + */ + @Test + public void hibernatePersistentSet() throws SerializationException { + PersistentSet object = new PersistentSet(); + Object deserialized = serializeBackAndForth(object); + assertThat(deserialized, is(not(instanceOf(PersistentSet.class)))); + assertThat(deserialized, is(instanceOf(Set.class))); + } + + /** + * Tests that the Hibernate {@link PersistentMap} can be serialized, but in way that + * deserialized class will be java map and but not {@link PersistentMap}. + * + * @throws SerializationException + * SerializationException + */ + @Test + public void hibernatePersistentMap() throws SerializationException { + PersistentMap object = new PersistentMap(); + Object deserialized = serializeBackAndForth(object); + assertThat(deserialized, is(not(instanceOf(PersistentMap.class)))); + assertThat(deserialized, is(instanceOf(Map.class))); + } + + /** + * Performs the serialization of the given object to bytes and then performs de-serialization + * from those bytes and returns the de-serialized object back. + * + * @param original + * Original object. + * @return De-serialized objects from bytes gotten from the serialization of original. + * @throws SerializationException + * If serialization fails. + */ + @SuppressWarnings("unchecked") + private T serializeBackAndForth(Object original) throws SerializationException { + ByteBufferOutputStream byteBufferOutputStream = new ByteBufferOutputStream(byteBuffer); + Output output = new Output(byteBufferOutputStream); + serializer.serialize(original, output); + byteBuffer.flip(); + ByteBufferInputStream byteBufferInputStream = new ByteBufferInputStream(byteBuffer); + Input input = new Input(byteBufferInputStream); + return (T) serializer.deserialize(input); + + } + +} diff --git a/CMR/test/info/novatec/inspectit/storage/serializer/schema/SchemaManagerTestProvider.java b/CMR/test/info/novatec/inspectit/storage/serializer/schema/SchemaManagerTestProvider.java new file mode 100644 index 000000000..35023941b --- /dev/null +++ b/CMR/test/info/novatec/inspectit/storage/serializer/schema/SchemaManagerTestProvider.java @@ -0,0 +1,36 @@ +package info.novatec.inspectit.storage.serializer.schema; + +import java.io.IOException; + +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; + +/** + * A test utility class that provides instances of {@link ClassSchemaManager} for testing purposes. + * + * @author Ivan Senic + * + */ +public final class SchemaManagerTestProvider { + + /** + * Private constructor. + */ + private SchemaManagerTestProvider() { + } + + /** + * Returns properly instantiated {@link ClassSchemaManager} that can be used in tests. + * + * @return Returns properly instantiated {@link ClassSchemaManager} that can be used in tests. + * @throws IOException + * If {@link IOException} occcurs. + */ + public static ClassSchemaManager getClassSchemaManagerForTests() throws IOException { + ClassSchemaManager schemaManager = new ClassSchemaManager(); + schemaManager.log = LoggerFactory.getLogger(ClassSchemaManager.class); + schemaManager.setSchemaListFile(new ClassPathResource(ClassSchemaManager.SCHEMA_DIR + "/" + ClassSchemaManager.SCHEMA_LIST_FILE, schemaManager.getClass().getClassLoader())); + schemaManager.loadSchemasFromLocations(); + return schemaManager; + } +} diff --git a/CMR/test/spring/spring-context-storage-test.xml b/CMR/test/spring/spring-context-storage-test.xml new file mode 100644 index 000000000..35e08e203 --- /dev/null +++ b/CMR/test/spring/spring-context-storage-test.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/Commons/.checkstyle b/Commons/.checkstyle new file mode 100644 index 000000000..79f52699f --- /dev/null +++ b/Commons/.checkstyle @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/Commons/.classpath b/Commons/.classpath new file mode 100644 index 000000000..4f3fc694e --- /dev/null +++ b/Commons/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Commons/.gitignore b/Commons/.gitignore new file mode 100644 index 000000000..0e01f1995 --- /dev/null +++ b/Commons/.gitignore @@ -0,0 +1,6 @@ +/bin +/dist +/lib +/build +/resources/java15runtime/ +/test-output diff --git a/Commons/.pmd b/Commons/.pmd new file mode 100644 index 000000000..988d84427 --- /dev/null +++ b/Commons/.pmd @@ -0,0 +1,7 @@ + + + true + resources/shared/config/pmd/pmd_rules.xml + false + false + diff --git a/Commons/.project b/Commons/.project new file mode 100644 index 000000000..5b41e6ad2 --- /dev/null +++ b/Commons/.project @@ -0,0 +1,41 @@ + + + Commons + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + net.sourceforge.pmd.eclipse.plugin.pmdBuilder + + + + + net.sf.eclipsecs.core.CheckstyleBuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.pde.PluginNature + org.apache.ivyde.eclipse.ivynature + net.sourceforge.pmd.eclipse.plugin.pmdNature + net.sf.eclipsecs.core.CheckstyleNature + + diff --git a/Commons/.settings/org.apache.ivyde.eclipse.prefs b/Commons/.settings/org.apache.ivyde.eclipse.prefs new file mode 100644 index 000000000..41fd7532e --- /dev/null +++ b/Commons/.settings/org.apache.ivyde.eclipse.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.apache.ivyde.eclipse.standaloneretrieve= diff --git a/Commons/.settings/org.eclipse.jdt.core.prefs b/Commons/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..0cf15cb8d --- /dev/null +++ b/Commons/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,13 @@ +#Mon Jun 20 09:21:22 CEST 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.5 +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore diff --git a/Commons/.settings/org.eclipse.pde.core.prefs b/Commons/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 000000000..c37ece907 --- /dev/null +++ b/Commons/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +#Mon Sep 03 14:49:10 CEST 2012 +eclipse.preferences.version=1 +resolve.requirebundle=false diff --git a/Commons/.settings/org.eclipse.pde.prefs b/Commons/.settings/org.eclipse.pde.prefs new file mode 100644 index 000000000..32c974f36 --- /dev/null +++ b/Commons/.settings/org.eclipse.pde.prefs @@ -0,0 +1,33 @@ +#Thu Jan 24 11:07:42 CET 2013 +compilers.f.unresolved-features=1 +compilers.f.unresolved-plugins=1 +compilers.incompatible-environment=1 +compilers.p.build=1 +compilers.p.build.bin.includes=1 +compilers.p.build.encodings=2 +compilers.p.build.java.compiler=2 +compilers.p.build.java.compliance=2 +compilers.p.build.missing.output=2 +compilers.p.build.output.library=1 +compilers.p.build.source.library=1 +compilers.p.build.src.includes=1 +compilers.p.deprecated=1 +compilers.p.discouraged-class=1 +compilers.p.internal=1 +compilers.p.missing-packages=2 +compilers.p.missing-version-export-package=2 +compilers.p.missing-version-import-package=2 +compilers.p.missing-version-require-bundle=2 +compilers.p.no-required-att=0 +compilers.p.not-externalized-att=2 +compilers.p.unknown-attribute=1 +compilers.p.unknown-class=1 +compilers.p.unknown-element=1 +compilers.p.unknown-identifier=1 +compilers.p.unknown-resource=1 +compilers.p.unresolved-ex-points=0 +compilers.p.unresolved-import=1 +compilers.s.create-docs=false +compilers.s.doc-folder=doc +compilers.s.open-tags=1 +eclipse.preferences.version=1 diff --git a/Commons/META-INF/MANIFEST.MF b/Commons/META-INF/MANIFEST.MF new file mode 100644 index 000000000..07e0a4224 --- /dev/null +++ b/Commons/META-INF/MANIFEST.MF @@ -0,0 +1,54 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: inspectIT Commons +Bundle-SymbolicName: info.novatec.inspectit.commons +Bundle-Version: 1.5.0 +Export-Package: info.novatec.inspectit.cmr.cache, + info.novatec.inspectit.cmr.model, + info.novatec.inspectit.cmr.property.spring, + info.novatec.inspectit.cmr.service, + info.novatec.inspectit.cmr.service.exception, + info.novatec.inspectit.cmr.storage.util, + info.novatec.inspectit.communication, + info.novatec.inspectit.communication.comparator, + info.novatec.inspectit.communication.data, + info.novatec.inspectit.communication.data.cmr, + info.novatec.inspectit.communication.valueobject, + info.novatec.inspectit.indexing, + info.novatec.inspectit.indexing.restriction, + info.novatec.inspectit.spring.logger, + info.novatec.inspectit.storage.nio, + info.novatec.inspectit.storage.nio.bytebuffer, + info.novatec.inspectit.storage.nio.stream, + info.novatec.inspectit.storage.serializer, + info.novatec.inspectit.storage.serializer.impl, + info.novatec.inspectit.storage.serializer.provider, + info.novatec.inspectit.storage.serializer.schema, + info.novatec.inspectit.storage.serializer.util, + info.novatec.inspectit.util, + info.novatec.inspectit.versioning, + schema +Bundle-Vendor: NovaTec GmbH +Bundle-RequiredExecutionEnvironment: J2SE-1.5 +Bundle-ClassPath: . +Require-Bundle: com.springsource.net.sf.cglib;bundle-version="2.2.0", + info.novatec.asm;bundle-version="4.0.0", + info.novatec.minlog;bundle-version="1.2.0", + info.novatec.reflectasm;bundle-version="1.7.0", + org.objenesis;bundle-version="2.1.0", + com.springsource.org.aopalliance;bundle-version="1.0.0", + org.springframework.aop;bundle-version="3.1.0", + org.springframework.asm;bundle-version="3.1.0", + org.springframework.beans;bundle-version="3.1.0", + org.springframework.context;bundle-version="3.1.0", + org.springframework.core;bundle-version="3.1.0", + org.springframework.expression;bundle-version="3.1.0", + org.springframework.web;bundle-version="3.1.0", + org.apache.commons.lang;bundle-version="2.5.0", + org.apache.commons.collections;bundle-version="3.2.1", + org.apache.commons.pool;bundle-version="1.6.0", + com.esotericsoftware.kryo;bundle-version="2.24.0", + info.novatec.kryo-serializers;bundle-version="0.23.0", + info.novatec.kryonet;bundle-version="2.21.0", + slf4j.api;bundle-version="1.7.5", + com.google.guava.jdk5;bundle-version="13.0.0" diff --git a/Commons/build.properties b/Commons/build.properties new file mode 100644 index 000000000..0ce295289 --- /dev/null +++ b/Commons/build.properties @@ -0,0 +1,4 @@ +jars.compile.order = . +source.. = src/,\ + test/ +output.. = bin/ diff --git a/Commons/resources/build.properties b/Commons/resources/build.properties new file mode 100644 index 000000000..f0ecd5cba --- /dev/null +++ b/Commons/resources/build.properties @@ -0,0 +1,34 @@ +dist.jar.name=inspectit-commons.jar +plugin.name=info.novatec.inspectit.commons + +## Ivy +ivy.file = ${basedir}/resources/ivy/ivy.xml + +## Some QA settings +build.root=${basedir}/build +build.classes.root=${build.root}/classes +build.release.root=${build.root}/release +build.qa.root=${build.root}/QA +build.qa.analysis=${build.qa.root}/static_analysis +build.qa.analysis.pmd=${build.qa.analysis}/pmd +build.qa.analysis.findbugs=${build.qa.analysis}/findbugs +build.qa.analysis.checkstyle=${build.qa.analysis}/checkstyle +build.qa.analysis.cpd=${build.qa.analysis}/cpd +build.qa.test=${build.qa.root}/functional_tests +build.qa.test.testdata=${build.qa.test}/testdata +build.qa.test.coveragedata=${build.qa.test}/coveragedata + +## Settings for TestNG +resources.testng=${basedir}/resources/testng + +##general settings +build.common-targets.file=${basedir}/resources/shared/build/common-targets.xml +build.commons.classes=${build.classes.root}/commons/classes +src.root=${basedir}/src +test.root=${basedir}/test +release.root=${basedir}/release +lib.root=${basedir}/lib +build.test.classes=${build.classes.root}/commons/test +build.instrumented.classes=${build.classes.root}/commons/instrumented + +java15runtime.path=${basedir}/resources/java15runtime \ No newline at end of file diff --git a/Commons/resources/build.xml b/Commons/resources/build.xml new file mode 100644 index 000000000..253e17777 --- /dev/null +++ b/Commons/resources/build.xml @@ -0,0 +1,194 @@ + + + + + Sophisticated Monitoring tool by NovaTec GmbH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Commons/resources/installer/installer-commons/Unix_shortcutSpec.xml b/Commons/resources/installer/installer-commons/Unix_shortcutSpec.xml new file mode 100644 index 000000000..533a6ee38 --- /dev/null +++ b/Commons/resources/installer/installer-commons/Unix_shortcutSpec.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Commons/resources/installer/installer-commons/Win_shortcutSpec.xml b/Commons/resources/installer/installer-commons/Win_shortcutSpec.xml new file mode 100644 index 000000000..42ae2d67f --- /dev/null +++ b/Commons/resources/installer/installer-commons/Win_shortcutSpec.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Commons/resources/installer/installer-commons/bin/langpacks/flags/eng-custom.gif b/Commons/resources/installer/installer-commons/bin/langpacks/flags/eng-custom.gif new file mode 100644 index 0000000000000000000000000000000000000000..3418042b5fd2ad914c2ee805e39b45e586e893ca GIT binary patch literal 1114 zcmeH`?@JSL9LK-s&N;Q_-1;%a%F9u^vT!lTVU(SwcT2-AAIy6Y=ow@q5bjZ&kZwm5 z&Qdud>ia;Ws~+?P2^Kv$QYo7h$DkNNaV;8reKw^B!zW&65Be*7@qY0BydS=Kf4aG@ z*2thA_&^B&Kt%w>04fSXf-53=7}3wfLxRi*5+h1cDaA{wC__F#N?cYXnwC|h zgd!9B0@zY zS4yW#x?VDjQZ84ru`CN%mT6iTTmLRQ*kPxE{|#)frv&{acSdi>+e&~W!mfDHs=lXD+t-gVvIH7|F%xWh@y`}|26x%zRs>Ee;f<&DCp z*|ylq=gsoF`|dd$CwutJ&+LmUFRx9$=VlHhxq~CUp+4|FLUoUvJT2t3@qcstJ-G9^*7#IwUE*0)f)gHQ8b#5e2 l{;8vav2W|39e^!j{e1b}F;D(B(b}GH)ixCJNrC`ve*u}B4;KIc literal 0 HcmV?d00001 diff --git a/Commons/resources/installer/installer-commons/bin/langpacks/installer/eng-custom.xml b/Commons/resources/installer/installer-commons/bin/langpacks/installer/eng-custom.xml new file mode 100644 index 000000000..decab06e8 --- /dev/null +++ b/Commons/resources/installer/installer-commons/bin/langpacks/installer/eng-custom.xml @@ -0,0 +1,298 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Commons/resources/installer/installer-commons/head-logo.png b/Commons/resources/installer/installer-commons/head-logo.png new file mode 100755 index 0000000000000000000000000000000000000000..50ab0b16de1986a1a4178d7487ee3e27496ac821 GIT binary patch literal 2691 zcmV-}3Vij6P)~A=zpo7-HN;{>a6)YQsSib~PRhV!I*HgC=bc9z2+K1;T+qmqZSv3auU*|IsCe zXb*-phI+umB1KIQZNQTkeZT2@&Fggj&zsqq+3%N3mZdxIn>X`*@B8z<_st6Cf)ndD z^ze^ve&ocFG}QS~8pK;(Nj=Pdo@FhK4#@cVZ&w0VL+h=a(g zUksBtEb-Lj5BSJxdq_G_6Hf>Pf<_{%>&9FA!VwLro&eUP&DmUXV@2PUs*!|3>&gUz zMju(-Y6y)P)UO3ep@A_mAQ?o>7&8+Z`glV!2}~=#D4I zg*Ml)Y&CrN_N^hWKW=Z|m{-2e&&}Pec{~r*I=vM7tMN8{y~o*fO+DcAGqdW50eCBAM!(DMaG1XQFHiR z4_kP@SCDeYAZM5IJ!gLIs^SaK{<8KsX)BzZMYrGCT%SMF8h|$a=Jhw01inBhl~`eo~4^$(l}(t*HXNUQ=fkR66Y-P92yFc2@=45os35cvQPdW@ex=0b!4ogEP* zo)!~N(0bqK^HreKQ(@L=h`eM@;&y@(>n|;9o2fVT+V`gnA|bf0PCE9ng zKgO7zI^S)ANLy?spuDE*9pUX&Ewq0>ODFKErqS4lEXj=WKIZycGScj|4VdI0H%P7{ zh$WY<&>n(wI=5T;gs#Cf$Omgh9jpQUzGXhL#l+L2b8>^mv~Uk}lB`X%SF+I9+U`vP z;b|iDoT4e~Z-I<{f=(b-ZS7lelSE|;zv0G)5J=&=~UVNer( zrtiTK#!?aSg0>j$a~K&2G?sn1BO{F%Twcs@-v#PHL>%x@VM0Sgn@wmCR(;ci6+q&t z12e|?SeDY^^Pb-R1nWM3&WN;!DNCdob&FX)@1lA925amviGY+vWV|C_n@-ljvjHZU zd7mW?Ww$_`2?Jt+nzZ?Q#bCEac^Ukk?8SVUA_o?kDvD^H|LX-+UY{H%Nl||jqvr?>hz^!>>Cle z5s=OyF_^u8t?UNa1Wr)7_GUMx?v5bYyW&`_AmFLE;Vk zJC?OFR!S)7ET0n^c5_Vr;*S~Dc3X*fJLoO$5HFc)76Bd@P4jKoD-8c0)yPuoY z_?{HATC<-WMyn4*Y~o8y#2{m`!H=W20VFo$I*}M?N%J6=OIJGHf{}2PL;8(ToA?tk zn3XlRjU_Qms`Unlty%4s%7jJ^fod8rTKKtP@^bSL-_v3YrezmvPIz)BS{NZMWG-n=N4@ zYOG8^03kLKAHIESKC7J*S}UyPs>VR#I*~XUB2P3)U(58kU2!=E8L>>Jc1bNMM29CL zb1hZZFq0=8b>7cR_fhOy?n%L==Ip9{XvRnc_jsrgL@pmY?kZOXyGmjZWHraNm~xWJ z+FEv71-%ygOA3&p+3!O;%gm{&4;ckf%d6(l=CIFYGi1CW*jTqx7R*EavVa&A+Zb)- z8bpTgcw*fKg!iX2NaiB%Up3`@2)nJ%D`D2g*eAdJ>ER^m@=0cl<-ALq=53p9MC@=d zchTM!zm-uvJ|joDi|fdIu*+_O8i!<^&%>QMrsiW&0o~q<^*d%0GMm# z(V%i|Q#4_c@&hGKd)Y#IU{d`|IRJ}-z(Z+RFIEO%6xWIg7>OO79{e>)_O5`wom)SP xVhq(Hyq6VWNls|KZmn&4B(uC + + + + + @{installer.application.name} + @{installer.application.version} + + + + @{installer.application.homepage} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OS_VER + Windows + + + ARCH_VER + 64 + + + ARCH_VER + 32 + + + + + + + + + + + + + + + + + @{inspectit.ui.description.long} + + + + + + + @{inspectit.cmr.description.long} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @{inspectit.agent.description.long} + + + + \ No newline at end of file diff --git a/Commons/resources/installer/installer-commons/license.html b/Commons/resources/installer/installer-commons/license.html new file mode 100644 index 000000000..1ba696125 --- /dev/null +++ b/Commons/resources/installer/installer-commons/license.html @@ -0,0 +1,685 @@ + + + +Copyright © 2008- Novatec Consulting GmbH + +

GNU AFFERO GENERAL PUBLIC LICENSE

+

Version 3, 19 November 2007

+ +

Copyright © 2007 Free Software Foundation, +Inc. <http://fsf.org/> +
+ Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed.

+ +

Preamble

+ +

The GNU Affero General Public License is a free, copyleft license +for software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software.

+ +

The licenses for most software and other practical works are +designed to take away your freedom to share and change the works. By +contrast, our General Public Licenses are intended to guarantee your +freedom to share and change all versions of a program--to make sure it +remains free software for all its users.

+ +

When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things.

+ +

Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software.

+ +

A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public.

+ +

The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version.

+ +

An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license.

+ +

The precise terms and conditions for copying, distribution and +modification follow.

+ +

TERMS AND CONDITIONS

+ +

0. Definitions.

+ +

"This License" refers to version 3 of the GNU Affero General Public +License.

+ +

"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks.

+ +

"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations.

+ +

To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work.

+ +

A "covered work" means either the unmodified Program or a work based +on the Program.

+ +

To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well.

+ +

To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying.

+ +

An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion.

+ +

1. Source Code.

+ +

The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work.

+ +

A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language.

+ +

The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it.

+ +

The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work.

+ +

The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source.

+ +

The Corresponding Source for a work in source code form is that +same work.

+ +

2. Basic Permissions.

+ +

All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law.

+ +

You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you.

+ +

Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary.

+ +

3. Protecting Users' Legal Rights From Anti-Circumvention Law.

+ +

No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures.

+ +

When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures.

+ +

4. Conveying Verbatim Copies.

+ +

You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program.

+ +

You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee.

+ +

5. Conveying Modified Source Versions.

+ +

You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions:

+ +
    + +
  • a) The work must carry prominent notices stating that you modified + it, and giving a relevant date.
  • + +
  • b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices".
  • + +
  • c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it.
  • + +
  • d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so.
  • + +
+ +

A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate.

+ +

6. Conveying Non-Source Forms.

+ +

You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways:

+ +
    + +
  • a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange.
  • + +
  • b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge.
  • + +
  • c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b.
  • + +
  • d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements.
  • + +
  • e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d.
  • + +
+ +

A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work.

+ +

A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product.

+ +

"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made.

+ +

If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM).

+ +

The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network.

+ +

Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying.

+ +

7. Additional Terms.

+ +

"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions.

+ +

When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission.

+ +

Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms:

+ +
    + +
  • a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or
  • + +
  • b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or
  • + +
  • c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or
  • + +
  • d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or
  • + +
  • e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or
  • + +
  • f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors.
  • + +
+ +

All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further restriction, +you may remove that term. If a license document contains a further +restriction but permits relicensing or conveying under this License, you +may add to a covered work material governed by the terms of that license +document, provided that the further restriction does not survive such +relicensing or conveying.

+ +

If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms.

+ +

Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way.

+ +

8. Termination.

+ +

You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11).

+ +

However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation.

+ +

Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice.

+ +

Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10.

+ +

9. Acceptance Not Required for Having Copies.

+ +

You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so.

+ +

10. Automatic Licensing of Downstream Recipients.

+ +

Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License.

+ +

An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts.

+ +

You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it.

+ +

11. Patents.

+ +

A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version".

+ +

A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License.

+ +

Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version.

+ +

In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party.

+ +

If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid.

+ +

If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it.

+ +

A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007.

+ +

Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law.

+ +

12. No Surrender of Others' Freedom.

+ +

If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program.

+ +

13. Remote Network Interaction; Use with the GNU General Public License.

+ +

Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph.

+ +

Notwithstanding any other provision of this License, you have permission +to link or combine any covered work with a work licensed under version 3 +of the GNU General Public License into a single combined work, and to +convey the resulting work. The terms of this License will continue to +apply to the part which is the covered work, but the work with which it is +combined will remain governed by version 3 of the GNU General Public +License.

+ +

14. Revised Versions of this License.

+ +

The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may differ +in detail to address new problems or concerns.

+ +

Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero +General Public License "or any later version" applies to it, you have +the option of following the terms and conditions either of that +numbered version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number +of the GNU Affero General Public License, you may choose any version +ever published by the Free Software Foundation.

+ +

If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that +proxy's public statement of acceptance of a version permanently +authorizes you to choose that version for the Program.

+ +

Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version.

+ +

15. Disclaimer of Warranty.

+ +

THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

+ +

16. Limitation of Liability.

+ +

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES.

+ +

17. Interpretation of Sections 15 and 16.

+ +

If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee.

+ +

END OF TERMS AND CONDITIONS

+ +

Additional permissions under GNU AGPL Version 3 Section 7:

+ +

The User Interface of InspectIT interoperates with Eclipse RCP solely via +Eclipse RCP's plug-in APIs and is linked with some specific components +licensed under the Eclipse Public License, Version 1.0 (hereinafter "EPL +Components").

+ +

These EPL components are: +

    +
  • Eclipse Nebula, +
  • c3p0, +
  • logback-core +
  • logback-classic, and +
  • AspectJ. +
+ +Should the interpretation of the GNU AGPL Version 3 under any applicable laws +result in the User Interface of InspectIT and Eclipse RCP and/or EPL Components +being a combined program, NovaTec GmbH herewith grants you the additional +permission to use and propagate the User Interface of InspectIT together with +Eclipse RCP and/or EPL Components with only the license terms in place for +Eclipse RCP and EPL Components applying to Eclipse RCP and EPL Components and +the GNU AGPL Version 3 applying for the Use Interface of InspectIT, provided +the license terms of Eclipse RCP and EPL Components themselves allow for the +respective use and propagation of Eclipse RCP and EPL Components together with +the User Interface of InspectIT.

+ +

Furthermore, NovaTec GmbH herewith grants you the additional permission to link +the User Interface of InspectIT together with the following Free and Open +Source Software components (hereinafter "FOSS Components"): +

    +
  • javax.servlet +
  • javax.transaction +
  • asm +
  • Hessian +
  • Hibernate +
+and to propagate the User Interface of InspectIT together with the +aforementioned FOSS components with only the license terms in place for the +FOSS components applying to the FOSS components and the GNU AGPL Version 3 +applying for the Use Interface of InspectIT. +

+ + + \ No newline at end of file diff --git a/Commons/resources/installer/installer-commons/service/inspectITCMRw.exe b/Commons/resources/installer/installer-commons/service/inspectITCMRw.exe new file mode 100755 index 0000000000000000000000000000000000000000..730240403a7daf5d3d0ad8e879b6b040c9668c83 GIT binary patch literal 104448 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⋙Y4Wrxk}FeDU+Zy7 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μ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 G)&B#j_9{*Q literal 0 HcmV?d00001 diff --git a/Commons/resources/installer/installer-commons/service/template_installService.bat b/Commons/resources/installer/installer-commons/service/template_installService.bat new file mode 100644 index 000000000..9d64d4863 --- /dev/null +++ b/Commons/resources/installer/installer-commons/service/template_installService.bat @@ -0,0 +1,35 @@ +:: Name: installService.bat +:: Owner: NovaTec Solutions GmbH (http://www.novatec-gmbh.de/, http://www.inspectit.eu/) +:: Description: installService.bat installs and configures inspectIT CMR as a Windows Service. + +@ECHO OFF + +:: Procrun execution binary +SET PR_INSTALL=$INSTALL_PATH\CMR\$PR_EXE + +:: Windows Service meta information +SET SERVICE_NAME=$SERVICE_NAME +SET PR_DESCRIPTION=$PR_DESCRIPTION +SET PR_DISPLAYNAME=$PR_DISPLAYNAME + +:: Java installation path +SET PR_JVM=$INSTALL_PATH\CMR\jre\bin\server\$PR_JVM + +:: Procrun classpath +SET PR_CLASSPATH=$INSTALL_PATH\CMR\$PR_CLASSPATH + +:: Startup configuration +SET PR_STARTUP=$PR_STARTUP +SET PR_STARTMODE=$PR_STARTMODE +SET PR_STARTCLASS=$PR_STARTCLASS +SET PR_STARTMETHOD=$PR_STARTMETHOD +SET PR_STARTPARAMS=$PR_STARTPARAMS + +:: Shutdown configuration +SET PR_STOPMODE=$PR_STOPMODE +SET PR_STOPCLASS=$PR_STOPCLASS +SET PR_STOPMETHOD=$PR_STOPMETHOD +SET PR_STOPPARAMS=$PR_STOPPARAMS + +:: Windows Service installation +"%PR_INSTALL%" //IS//%SERVICE_NAME% #COMMAND_OPTS# \ No newline at end of file diff --git a/Commons/resources/installer/installer-commons/service/template_uninstallService.bat b/Commons/resources/installer/installer-commons/service/template_uninstallService.bat new file mode 100644 index 000000000..b4bb8cf47 --- /dev/null +++ b/Commons/resources/installer/installer-commons/service/template_uninstallService.bat @@ -0,0 +1,10 @@ +:: Name: uninstallService.bat +:: Owner: NovaTec Solutions GmbH (http://www.novatec-gmbh.de/, http://www.inspectit.eu/) +:: Description: uninstallService.bat removes inspectIT CMR Windows Service. + +@ECHO OFF +SET PR_INSTALL=$INSTALL_PATH\CMR\$PR_EXE + +"%PR_INSTALL%" //DS//$SERVICE_NAME 2> NUL + +SET ERRORLVL=0 \ No newline at end of file diff --git a/Commons/resources/installer/installer-commons/service/win32/prunsrv.exe b/Commons/resources/installer/installer-commons/service/win32/prunsrv.exe new file mode 100755 index 0000000000000000000000000000000000000000..4240720018bf2e34322a3755bd73a2a31a220ce7 GIT binary patch literal 80896 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�}yf-3CCwqWe~KaW8E2#&)Y{zAX(-n9Wy z9o2CdTZG=h)k=M%y4MCrbv(=W2;Q+}D0BLDiXT}BNg>fr$k2TeOXq0!MJ%kLY^(@n zye^6W+k>5(9+z(?ET1M}_NIp29dI@Egv_zct z0sQvf72tKzv-};ii@%pY#oy^W`CIe^ep~Mvb6nmoIxKIOKQ3<>JLD~QtGwksjN8Na zWE{mUTp_mam-qDThj7P1QQKeT`^FP!{v6E6!gC7#w$X9>VMO|1MlXJcrHkH!8T;`B z^{Ij$2Rp*cw#|qY&UM5iTk|JjE6yj-_Qc`Je55~&ec=&$OGlJn+!xJVi+eQp7WvzS zU$v{2szXpZXZ5(pXKeypa{}2zj<-*#IEM|kdh|#X`v6s(GYkOb7CK6f?T|?9v+i)E zN})yOylhsF*{B{Tprn@-V61w~MD^Go-iHd1$KD>`J?-Vb`LepqLOnH-mBj$nWwxv? zvtrfdNK}`jQC((s3|n{&Vlb{@e0Jd*c*?}n$kV?*eZ2eEBK$8X#9n7O|A5LAC4c>* z-CLo(z7fU)#2eXjBUaj`_pBM&xTtpVk`+sqp%#iBHDjXMDbhwzh8b3$V(bA~Iw$Qp z!`AI=>g5}{{hrc@0d6Y$435rm*ZZJKL4Ne5fZ<-)8@USX z_r>HzR+e?iHQj5o(n7i6lI$Wakly_qf*y68L zoO?zUIv0no2``$n@z^cnvCShEKm;z^ls+7dL6sEPGGu6BZwX^n*bA%?wMaEMB57sR zm~jq6GUyE(Q=@T@K0)wHqn|EWj_)t2p^Vk3i&yN!V_FEC#FSuqYego0SY>s;nu=FOLq>|o!Kvxj z0(g456NePu0Js)#6(Ads4tNK2#{s>7#{ruGHv?7!vf*zo?vwZo7y@`7xZ{9bfQJEh z0d4`T1}p~fdJHWKwk{3+Lr2wv;x*2dAU<=?0Kri4|fVTlS zH$sd;4!u0SQz#KpopcQa4;4Z*-0nY-C z0Dc2_50H)-CSL+v3z!S21-Jn>1MUQT8?Xy-6c7QN0o;jjZUMLf%K-}ka{&2(F9Sva z`mxF^0yqXZ0C)!QL%_p;+W>0;t$?L~DnJRK0FVR71_;21pMVG82w)GOWEkQG7y|eh zJ8OR*xGjJ#KoH;roP!>g%kK9QKY_DBCbT^$-Ejpe%n+n3wh9d4IL&$q*Kfww=InyOpML+!p&x4S-Qv#$vdx3IEhz8);|>$afoxWU$8YpOQ! z3aN0E!JsYITWukCP~j^*u3)LZjb22(9+c`Rud31ly3KEQYCc=gsRg~-)w%UoiyNW4 zI>fgEF)I{P>4kpE-R^Q1QZ(rGuWKgG=hxj{n*)g}y@}<}Pe61CJ9HBi*NG`e35V1k z@+1hE1gksR4Qk|3u}Zk0=jDbzJET^Zqj%l`PI14D0s6ebuWO9U0&lynSX6pCY;KoB zs|$I8u6Di3wMyZwx?c?+t358M`vK?!KHctWb;Y6}a-pG$wJ!96z1}rirweM)`D*t% zU@8ugzuG1fem3HtS5EHvcDF4M$fpL2U_UZ}l+LwzHkVs>h#L^+8fo^+avdu)1|jkm z2Lic{HMqC0&2_Z%j(6A{0;6s_T77<(C)mnoQ*IfMOex6%`6LkX`MiD@goZTNI&^

Ow0Ypa9 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


i<)Hajzhy{Az;;^2j`8c~ z4j=oRQ}NceW=^HwfYaR!FXIU)4d5bvhoh5(I;IvOF#3a*>8q^)C3b;
Nvj!h``iXTU++|TQ!3MkxeBOsM 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 + + Welcome +
+ inspectIT offers free performance analysis for all Java applications. inspectIT gives you everything you need to carry out performance diagnostics and monitoring on your Java applications.
+
+ Development +
+ inspectIT is developed by the employees of the Competence Area "Application Performance Management" of NovaTec Consulting GmbH.
+
+ Documentation +
+ Detailed documentation on inspectIT can be found at
inspectIT Confluence. Visit official website for further information on inspectIT. Visit NovaTec Consulting GmbH for further information on the company implementing inspectIT. The NovaTec Blog talks on technical topics and provides best practices on the usage of inspectIT. + + \ No newline at end of file diff --git a/Commons/resources/ivy/ivy.xml b/Commons/resources/ivy/ivy.xml new file mode 100644 index 000000000..bb73c0037 --- /dev/null +++ b/Commons/resources/ivy/ivy.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Commons/resources/shared/build/cmr-startup.properties b/Commons/resources/shared/build/cmr-startup.properties new file mode 100644 index 000000000..8fb68fb21 --- /dev/null +++ b/Commons/resources/shared/build/cmr-startup.properties @@ -0,0 +1,12 @@ +## Different JVM settings +## Because we use ANt replaceregexp '\' char needs to be represented with 8 times '\' +cmr.java.command.linux=./jre/bin/java +cmr.java.command.win=jre\\\\\\\\bin\\\\\\\\java.exe +cmr.java.memory.32bit=-Xms1024m -Xmx1024m -Xmn384M -XX:MaxPermSize=128m -XX:PermSize=128m +cmr.java.memory.64bit=-Xms1536m -Xmx1536m -Xmn512M -XX:MaxPermSize=192m -XX:PermSize=128m +cmr.java.opts.32bit=-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+DisableExplicitGC -XX:SurvivorRatio=4 -XX:TargetSurvivorRatio=90 -XX:BiasedLockingStartupDelay=500 -XX:+UseFastAccessorMethods -XX:+UseBiasedLocking -XX:+HeapDumpOnOutOfMemoryError +cmr.java.opts.64bit=-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+DisableExplicitGC -XX:SurvivorRatio=5 -XX:TargetSurvivorRatio=90 -XX:AutoBoxCacheMax=20000 -XX:BiasedLockingStartupDelay=500 -XX:+UseFPUForSpilling -XX:+UseFastAccessorMethods -XX:+UseBiasedLocking -XX:+UseCompressedOops -XX:+HeapDumpOnOutOfMemoryError +cmr.java.opts.linux=-server +cmr.java.opts.win= +cmr.java.locgc.linux=-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -Xloggc:logs/gc.log +cmr.java.locgc.win=-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -Xloggc:logs\\\\\\\\gc.log \ No newline at end of file diff --git a/Commons/resources/shared/build/common-targets.properties b/Commons/resources/shared/build/common-targets.properties new file mode 100644 index 000000000..d91a7e5d1 --- /dev/null +++ b/Commons/resources/shared/build/common-targets.properties @@ -0,0 +1,126 @@ +## Set up for shared resources +## ${commontargets.basedir} is defined in the build +shared.resources=${commontargets.basedir}/.. +shared.resources.config=${shared.resources}/config + +## The version of ivy we will use. Change this property to try a newer one +## Ivy is also downloaded from Novatec Nexus +ivy.install.version=2.2.0 +ivy.download.url=http://repo1.maven.org/maven2/org/apache/ivy/ivy/${ivy.install.version}/ivy-${ivy.install.version}.jar +ivy.jar.dir=${user.home}/.ant/lib +ivy.jar.file=${ivy.jar.dir}/ivy.jar +ivy.settings.file=${shared.resources.config}/ivy/ivysettings.xml + +## Settings for FTP +ftp.user=inspectit +ftp.pw=g1ticinobu +ftp.server=ntftp.novatec-gmbh.de + +# internal branch of the FTP, not visible to anonymous +ftp.internal.basedir=inspectit_internal + +ftp.internal.java15runtime.path=${ftp.internal.basedir}/java15runtime +ftp.internal.java15runtime.file=rt.jar + +## Settings for JVMs +jvm.remotedir=${ftp.internal.basedir}/jvm_installations + +## Settings for PMD +pmd.config.root=${shared.resources.config}/pmd +pmd.rules.file=${pmd.config.root}/pmd_rules.xml +pmd.report.file=${pmd.config.root}/pmd-report.xslt +pmd.sortable.file=${pmd.config.root}/sorttable.js + +## Settings for checkstyle +checkstyle.config.root=${shared.resources.config}/checkstyle +checkstyle.config.file=${checkstyle.config.root}/inspectit-checkstyle.xml +checkstyle.report.file=${checkstyle.config.root}/checkstyle.xsl +checkstyle.max.errors=0 +checkstyle.max.warnings=0 + +##Settings for FindBugs +findbugs.config.root=${shared.resources.config}/findbugs +findbugs.config.fancy-hist=${findbugs.config.root}/fancy-hist.xsl +findbugs.config.include=${findbugs.config.root}/findBugsIncludeFilter.xml +findbugs.config.exclude=${findbugs.config.root}/findBugsExcludeFilter.xml + +##Settings for CPD +cpd.config.root=${shared.resources.config}/cpd +cpd.report.file=${cpd.config.root}/cpdhtml.xslt + +## installer +commons.dir = ${basedir} +commons.installer.resources = ${commons.dir}/resources/installer/installer-commons +installer.windows64.name = inspectit.installer-all.win.x64.jar +installer.windows32.name = inspectit.installer-all.win.x86.jar +installer.linux64.name = inspectit.installer-all.linux.x64.jar +installer.linux32.name = inspectit.installer-all.linux.x86.jar + +output.dir = ${commons.dir}/build/installers/ + +## agent +agent.release.dir = ${basedir}/../Agent/release +agent.build = ${agent.release.dir}/inspectit-agent-sun1.5.zip +agent.installer.working.dir = ${agent.release.dir}/installer +agent.installer.name = inspectit-agent-sun1.5-installer + +## cmr +cmr.release.dir = ${basedir}/../CMR/release + +cmr.build.linux64 = ${cmr.release.dir}/inspectit-cmr.linux.x64.tar.gz +cmr.build.linux86 = ${cmr.release.dir}/inspectit-cmr.linux.x86.tar.gz +cmr.build.win64 = ${cmr.release.dir}/inspectit-cmr.windows.x64.zip +cmr.build.win86 = ${cmr.release.dir}/inspectit-cmr.windows.x86.zip + +cmr.installer.working.dir = ${cmr.release.dir}/installer + +## inspecit +inspectit.release.dir = ${basedir}/../inspectIT/release/dist + +inspectit.build.linux64 = ${inspectit.release.dir}/inspectit-linux.gtk.x86_64.zip +inspectit.build.linux86 = ${inspectit.release.dir}/inspectit-linux.gtk.x86.zip +inspectit.build.win64 = ${inspectit.release.dir}/inspectit-win32.win32.x86_64.zip +inspectit.build.win86 = ${inspectit.release.dir}/inspectit-win32.win32.x86.zip + +inspectit.installer.working.dir = ${inspectit.release.dir}/installer + +## Installer meta information +installer.application.name = inspectIT +installer.application.version = 1.5 +installer.author.email = info.inspectit@novatec-gmbh.de +installer.author.name = NovaTec Consulting GmbH +installer.application.homepage = http://www.inspectit.eu/ + +## inspectIT descriptions +inspectit.ui.description.long = The user interface of inspectIT. Install this component if you want to analyze the measurement data from this computer. +inspectit.cmr.description.long = The CMR (central measurement repository) collects the measurements from the inspectIT agents and provides them to the inspectIT user interface. Install this component if you want this computer to serve as your inspectIT central. +inspectit.cmr.description.short = Central measurement repository. +inspectit.cmr.winservice.description.long = Windows Service for inspectIT CMR. +inspectit.agent.description.long = The Java Agent integrates with Java applications and sent measurement data to the CMR. Install this component if you want to monitor an application running on this computer. + +## Windows Service templates +inspectit.windows.service.install.template = ${commons.installer.resources}/service/template_installService.bat +inspectit.windows.service.uninstall.template = ${commons.installer.resources}/service/template_uninstallService.bat + +## Windows Service meta settings +inspectit.windows.service.name = inspectITCMR +inspectit.windows.service.description = inspectIT Server - http://www.inspectit.eu/ +inspectit.windows.service.64.display.name = inspectIT CMR (64 Bit) +inspectit.windows.service.32.display.name = inspectIT CMR (32 Bit) + +## Windows Service java settings +inspectit.windows.service.jvm.path = jvm.dll +inspectit.windows.service.jvm.classpath = inspectit-cmr.jar + +## Procrun specific settings +procrun.jvmopts.prefix = ++JvmOptions= +procrun.exe = prunsrv.exe +procrun.startup = auto +procrun.startmode = jvm +procrun.startclass = info.novatec.inspectit.cmr.CMR +procrun.startmethod = start +procrun.startparams = start +procrun.stopmode = jvm +procrun.stopclass = info.novatec.inspectit.cmr.CMR +procrun.stopmethod = stop +procrun.stopparams = stop \ No newline at end of file diff --git a/Commons/resources/shared/build/common-targets.xml b/Commons/resources/shared/build/common-targets.xml new file mode 100644 index 000000000..cf51de9cc --- /dev/null +++ b/Commons/resources/shared/build/common-targets.xml @@ -0,0 +1,712 @@ + + + + + This build file has the common targets used by all other inspectIT components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Press Return key to continue and to overwrite the JRE: ${file} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Commons/resources/shared/coding-templates/inspectit-cleanup.xml b/Commons/resources/shared/coding-templates/inspectit-cleanup.xml new file mode 100644 index 000000000..b626de855 --- /dev/null +++ b/Commons/resources/shared/coding-templates/inspectit-cleanup.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Commons/resources/shared/coding-templates/inspectit-codetemplates.xml b/Commons/resources/shared/coding-templates/inspectit-codetemplates.xml new file mode 100644 index 000000000..ccac25994 --- /dev/null +++ b/Commons/resources/shared/coding-templates/inspectit-codetemplates.xml @@ -0,0 +1,38 @@ + diff --git a/Commons/resources/shared/coding-templates/inspectit-formatter.xml b/Commons/resources/shared/coding-templates/inspectit-formatter.xml new file mode 100644 index 000000000..928250f75 --- /dev/null +++ b/Commons/resources/shared/coding-templates/inspectit-formatter.xml @@ -0,0 +1,279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Commons/resources/shared/config/checkstyle/checkstyle.xsl b/Commons/resources/shared/config/checkstyle/checkstyle.xsl new file mode 100644 index 000000000..5b84934aa --- /dev/null +++ b/Commons/resources/shared/config/checkstyle/checkstyle.xsl @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

CheckStyle Audit

Designed for use with CheckStyle and Ant.
+
+ + + +
+ + + +
+ + + + + +

+

+ +


+ + + + +
+ + + + +

Files

+ + + + + + + + + + + + + + +
NameErrors
+
+ + + + +

File

+ + + + + + + + + + + + + +
Error DescriptionLine
+ Back to top +
+ + + +

Summary

+ + + + + + + + + + + + +
FilesErrors
+
+ + + + a + b + + +
+ + diff --git a/Commons/resources/shared/config/checkstyle/inspectit-checkstyle.xml b/Commons/resources/shared/config/checkstyle/inspectit-checkstyle.xml new file mode 100644 index 000000000..bd8eba4ff --- /dev/null +++ b/Commons/resources/shared/config/checkstyle/inspectit-checkstyle.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Commons/resources/shared/config/cpd/cpdhtml.xslt b/Commons/resources/shared/config/cpd/cpdhtml.xslt new file mode 100644 index 000000000..901267723 --- /dev/null +++ b/Commons/resources/shared/config/cpd/cpdhtml.xslt @@ -0,0 +1,98 @@ + + + + + + + + + + + + + +

Summary of duplicated code

+ This page summarizes the code fragments that have been found to be replicated in the code. + Only those fragments longer than 30 lines of code are shown. +

+ + + + + + + + + + + + + +
# duplicationsTotal linesTotal tokensApprox # bytes
+

+ You expand and collapse the code fragments using the + buttons. You can also navigate to the source code by clicking + on the file names. +

+ + + + + + + + + + + + + + + +
IDFilesLines
+ + + + +
../src/.html# line
+
# lines :
+ + + +
+ + + +
+

+ + + + + + + diff --git a/Commons/resources/shared/config/findbugs/fancy-hist.xsl b/Commons/resources/shared/config/findbugs/fancy-hist.xsl new file mode 100644 index 000000000..faa1b81f0 --- /dev/null +++ b/Commons/resources/shared/config/findbugs/fancy-hist.xsl @@ -0,0 +1,1197 @@ + + + + + + + + + + + + + + + + + + FindBugs (<xsl:value-of select="/BugCollection/@version" />) + Analysis for + <xsl:choose> + <xsl:when test='string-length(/BugCollection/Project/@projectName)>0'><xsl:value-of select="/BugCollection/Project/@projectName" /></xsl:when> + <xsl:otherwise><xsl:value-of select="/BugCollection/Project/@filename" /></xsl:otherwise> + </xsl:choose> + + + + + + +

+ FindBugs () + Analysis for + + + + +

+ + + +
+ +
+
+ Computing data... +
+ + +
+
+

Package Summary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PackageCode SizeBugsBugs p1Bugs p2Bugs p3Bugs Exp.
+ Overall + ( packages), + ( classes) +
+
+ +
+
+

Analyzed Files:

+
    + +
  • +
    +
+
+
+

Used Libraries:

+
    + +
  • +
    + +
  • None
  • +
    +
+
+
+

Analysis Errors:

+
    + + +
  • None
  • +
    + +
  • Missing ref classes for analysis: +
      + +
    • +
      +
    +
  • +
    +
+
+
+
Loading...
+
Loading...
+
Loading...
+
+ + + + +
+ + + + diff --git a/Commons/resources/shared/config/findbugs/findBugsExcludeFilter.xml b/Commons/resources/shared/config/findbugs/findBugsExcludeFilter.xml new file mode 100644 index 000000000..c15035ca8 --- /dev/null +++ b/Commons/resources/shared/config/findbugs/findBugsExcludeFilter.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Commons/resources/shared/config/findbugs/findBugsIncludeFilter.xml b/Commons/resources/shared/config/findbugs/findBugsIncludeFilter.xml new file mode 100644 index 000000000..aec54ba2a --- /dev/null +++ b/Commons/resources/shared/config/findbugs/findBugsIncludeFilter.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/Commons/resources/shared/config/ivy/ivysettings.xml b/Commons/resources/shared/config/ivy/ivysettings.xml new file mode 100644 index 000000000..e322fb5c3 --- /dev/null +++ b/Commons/resources/shared/config/ivy/ivysettings.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/Commons/resources/shared/config/pmd/pmd-report.xslt b/Commons/resources/shared/config/pmd/pmd-report.xslt new file mode 100644 index 000000000..d9039bd0d --- /dev/null +++ b/Commons/resources/shared/config/pmd/pmd-report.xslt @@ -0,0 +1,98 @@ + + + + + + + + + + +p1 +p2 +p3 +p4 +p5 + + + + - + + + + + + PMD <xsl:value-of select="//pmd/@version"/> Report + by NovaTec Consulting GmbH. All rights reserved.
The inspectIT Logo is a registered trademark of NovaTec Consulting GmbH.

+ + + + + + + \ No newline at end of file diff --git a/inspectIT/content/static/fonts/Abel.woff b/inspectIT/content/static/fonts/Abel.woff new file mode 100644 index 0000000000000000000000000000000000000000..32d3a137e548dbf8de062d2f7c632630592cb0f3 GIT binary patch literal 15768 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 WK|FbaBvfA}18N2S4z2}@2KrwCppsz# literal 0 HcmV?d00001 diff --git a/inspectIT/content/static/graphics/background.jpg b/inspectIT/content/static/graphics/background.jpg new file mode 100644 index 0000000000000000000000000000000000000000..50ffc8a26a1e4ab46548140fa29e95ce8b9d827e GIT binary patch literal 110065 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¥fQfqlJHJLp?POjFR`Lr1V&K+lXZ`*?t- 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 M|6geVl0O#y4*{uNX8-^I literal 0 HcmV?d00001 diff --git a/inspectIT/content/static/graphics/backgroundDown.png b/inspectIT/content/static/graphics/backgroundDown.png new file mode 100644 index 0000000000000000000000000000000000000000..d900c0eb731cdce59878d69b19cd25d2933bc6b3 GIT binary patch literal 252 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≷%du~lNJw>7a-UN3@gluD6)3S|EQ$eMTN&^y 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 Z0RY@GHwu?2rw9N5002ovPDHLkV1noBNLBy< literal 0 HcmV?d00001 diff --git a/inspectIT/content/static/graphics/produktkarton_inspectit_small.png b/inspectIT/content/static/graphics/produktkarton_inspectit_small.png new file mode 100644 index 0000000000000000000000000000000000000000..52a958273c7cca014b32fc5bf94775b695d39308 GIT binary patch literal 23418 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 literal 0 HcmV?d00001 diff --git a/inspectIT/content/static/graphics/welcomeActive.png b/inspectIT/content/static/graphics/welcomeActive.png new file mode 100644 index 0000000000000000000000000000000000000000..cb52530f4fb9490d463992158bdcac9e891fa997 GIT binary patch literal 15593 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 literal 0 HcmV?d00001 diff --git a/inspectIT/content/static/graphics/welcomeInactive.png b/inspectIT/content/static/graphics/welcomeInactive.png new file mode 100644 index 0000000000000000000000000000000000000000..60cea05cce87cea83984bb10574e37f3b4185bbd GIT binary patch literal 15686 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 M07*qoM6N<$f*hoIBme*a literal 0 HcmV?d00001 diff --git a/inspectIT/content/static/icons/documentationIcon.png b/inspectIT/content/static/icons/documentationIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..5000a59c06f4cae0117c332ad39e3239ab64c1df GIT binary patch literal 1951 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
|SnfSC*stF54B=XY0&i%zz{{C5Q)qyXR|#r&bS zcQsyEYfhR|S()|l1P{e28{KGDk#Y-|a|@9Iq(5+zNwp|JPlX}dkcuKD;6|EwNR&vD z=Lvf09c&IDCV=XQl2N>Jk$-~t1AGKH5SOO~tjp5~>y0!_C{$dax)`A!2SJYNRzFRz zepHA`3ph^Hjpi8fudf;SM|nKmKBc<(So`D#i60$e)NO-Ot&U+F*$+O zEI=y{Dm8GUZw%&302|VEJ(=n~neLeQ?o1ox{}_J?FaVPt`2Y%j9`yhK002ovPDHLk FV1hKhGByAJ literal 0 HcmV?d00001 diff --git a/inspectIT/content/static/icons/homeIcon.png b/inspectIT/content/static/icons/homeIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..098258971797f338d0d5a0bccea640a01a9a0453 GIT binary patch literal 1822 zcmV+(2jTdMP)=Fd#T65L zLOR1tI?e=$rlyq2OF{og{wN=Tm$4W~O zQ$t`<7ONoK8p*W7;-JKhWc2`rS#YM6U=i*k49-%)-(Y#clunt?4U^$txIwk^G zH2B>U%~7uUm;kEi{MzZ;Eo=ZQS|iv77~FGKf9#Ox^3v7?9i7wTiZfvSI? zs?~K$b@(oqbE+i?#>1=E&Upj6gWS~|?J20*236I<#Ij>says{P>LuEN0@p8KpC3A6 z165KyXR|%1ntGqROO}=$*FNrOxp=PP+KzqfMx9RjXdu8bSy@?qGQGWEffPVx4js~+ zE{y$Ft^Q@vl8ayKb^2E5HUKqN-)(GdZ4bCTIaKkwOlI{Ki+S1jU~8n>Z(L2q)tc7E z`Z_ZsQ&__%hI#J%ke9?|C9zuuYYdz1LA{$)^o^n0Ab@H@M+cGt`Nzaho0z%IJJ-jD z9fJHUOB?bEyjJCGFFX(3_WO+utVgCqP}j<$vib?i1k?r8cD~^1jcvQT)`D68Mj5G; zQzXQ9AKbJ#E(W?jIIr8lP`I)@au@76y)RIh5tr&@QHO;z~M? z9uGbOyGnEb^*w+>U#jgdsONzT5Alvr$SpoJEn>z7v9s*JvB1j-;B2PEQl4s;D5t2>nf_>gG-f%)E7N@A1aV~6># z%V^7Z_(;)=-yjJ~!$U-i;METg4A=xML?u;TdXs8yQRV}BOKF9=I59ClFEe9$NbF4Q z!k9O;oA%_+pN&H97M2hM^rVpP96ya=YgB~pfR{pQO-ZZU+T5zFg7Z~(vD9({*pxsL zK}H-H6o}E#9X+RTH9WI%49Gk-h7Ct5XJ1b&s4Ud41ak}EbqI;Z-pU;w*TZ!k6Rg-pKcvd7PI9p9^!BunOp(J5Z}PdLjdtF$AwT+cpLf?sF~z4 zm7R>zNubzDEW(eZ1z^^Gn-WfX4k`^j5$@FK@Mf`=ZhZK zarqYkxqi_OhqkkV(1HtKb>NPSM?da*yOfe5bHHRg`lI+?wSNQ{0InU_rBp>m$p8QV M07*qoM6N<$f_F}3SpWb4 literal 0 HcmV?d00001 diff --git a/inspectIT/content/static/icons/newFeaturesIcon.png b/inspectIT/content/static/icons/newFeaturesIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..b35ab728272e7b8a65b83a512deebee71aab19b2 GIT binary patch literal 2950 zcmV;13wiX3P)NWWQjo> zhM8ez?ksnv@0-s3y%AA+zSsM``TfqEd(S=R-2eHO|Mz{D?Ql3)kMi5PJ&E0LvjH#J zJv?^!`TMcnTD0KNHpfjL1=s*Te=c-=G5dbygW=FK4{%1YwXd-ekdp=;Aj_>+Yso(fbkxtvb5C5 z(LUbBc*??6ZAvt+JaXuahRtRL<8z&kug=KjpE8;3dOn}S>g&Vx4jZGLiqHJ}O9w-D zdH3#VLx%+GV+|&L-MY>C_!DV`EwFDFsM!7>72j84u$WhHdh(m@N@_Ek<8U6pV03Fe zaZ0#$Vi@&-OltM3y)M7_;`Fp_@Td)Oh71e#IK20_9y=lb5db>GVq345 zBg{MBSP(uE9!v9cbGQ$c7#e!^@u^|Hk9XxDXaLpm-bd|QN{cQue>e4x%LMEjX9<(rrRYvb;dA)gh>6G8JDe*uQ_;1b<)COo`Ys%FWH;;js6$8Yx~&henW} zo@3j+?dz=8p!0@PZG=-z$Fbt` z%A@mRQe)xr2J%sCT!hb*=c(ONiS@brxjId=Etl)$TW3Ze`x*ljtrnPB$XOX5Dj8vj+^-NrE1|?fr>rgv&mf;vF^7aFPK9 zHrQEOt~hh_xIFoIuHpxvj|u=qbGZ)wkU`q_Q&J*V!!$2wqM#>a3INp)*EKZB>Z+@o z8;M=opmEXZV&*So-z6~|1kRc%mzrZ?Q(tIeX2LhSgxj}o$x1;xE6Sg=y_b@jhU4d} zl!Z%|ojszyt=14*Lv;zKM2!tv{;qUs?_N|NoR^X&TfXYE^uu_L)44C~@b)xLWA+B` z)l(Gj%C3W~g<#9qQL-R88;j8`8nakv{SZzR^ELxIc&Ov`dy7&mkay*mrp)oS=&t5R07Z(2>}uCZ1zAQCu`|RxgFw1C5ll zdx9{zxBYFjF|*uen6@# z#{NaO9n&1p0<~>NTQbQ!Opkk;<`Mp~G`C8w!~2R(7b*5^+LWJ0B7_;F?{Fnp9?1;Q zJCu{AjtN~C$NwHUjRW*?F(N8`3E?YIMq0$?fd2Xj=Cly=ZbJgt1c%@%WKQglQiX6;doD@{8sB0{!Uz{rYy@m4jvG%Hx(V zOFKZ=93>jk3p$tfJz->@Ljbl zf?E7u<+{cjjVNu*PC7BpKD0{0LKKZPZe{xE9sAv0gQB*Hd4}SDY1ZWrTUsSmzj-8$ zNuWDPSv;a=j|)an_$)nJnEv^C`&#fs6|??AjiNAmcK%uds+p8;w%=V>lF;IYY90FI z;J)uH4qDLELTf+&EIl8EmVPW^S)iYOn%T@Xx2VMU5)JT}1Hz0KcyuW4v3?6Le($c_ z!XbYeWMBGD@boKn)|U10nPtRN8BmsgtKfA3$0hWjaH)7~hf|`Kgam0<+%M-xyPP?% zb^gW5D;0z;IK=yCk`{G6s0x47k1E`;&yDOmZ;tP~N`-xxSVVhZ+JYVoTt;mn40jBcy~o(*OVf07*qoM6N<$g5?3HOaK4? literal 0 HcmV?d00001 diff --git a/inspectIT/content/static/icons/supportIcon.png b/inspectIT/content/static/icons/supportIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..67e18b409a4091b3373b274f987ec51fa18306cf GIT binary patch literal 1602 zcmV-I2EF--P)wLJZ&l4eRX%|?YY}KIxNew+luifr*~$$bN9R7`ObIGB2`r>JXWlH3&Dh_ zfe+Q4@1|a>T%uC{!ax|<2zNd+@5RS$PN&fpiO7tis3`%IBvszEv&jROVF;o9Qpcdd zq2(*8kicKFZKyU}WLecQ(1lt~*sESyRR1YldI6X;SYRv$=XP5iEE{%+BI0k}GIt`R zCcR@*nryS%c$Xqm1#oMH2!8bOHhHiq#lR?>K0F74#_kzq`#!{%BrS3xw(*}0n`Te1 zDB~B4qRPQ74*I=!H>D(nc$s8>UK#8vsth%(00m#-OzP12Ecd=Rn-yC@6n`^YQ`{ipN5-fHKng zERIt+Sw>E{9RlGYgv4b`Guo(#A^uKdFen?~hxyzMgm!2Dm;%`7X(4^-cc7D2G~6gN?mcYi*1mD3(s z;B-DQ>9ZXNFJlV?U{XRK`?{{VZ~C;^>lhY|f#ZcM9vvQ+ofRrgGjdTeU%g>z?YzQv zFbrjUq{LUMdtW(vLjA>_VLHSMv=s=uqbP3nFl{hpA3wz%8~qSt{citJ&2FSbM$Qsi z#mdTtI()KVP0lQTw0UinsoIty;A)|MJHxiGT+y(1R8gUE&#v53oo`VaYxcHntf4St z!*F_H8UdAv+4$xg4JTket*^YUya$e!L7G)6pRWAT`XGCOTXDYavJtxDF%-)p)ju-E}WWq7&o)g|?N;PwzM zSj-A%pg9c~r~{GrwPPsJxk@!R9l&d9CqndG1x=!qfa$^E!Xhb9ST<^e{VN+NQ&I7# z+zgwAO1E$UL^OA&;Ucx==Q9DH$BR=L0^4B6PZ*ig+Z%E!Y7}5*;oEtb|yVF6roJu^a(eX zlT8mR8RMOWuNOW~FL2l+nfUtj@3tdt7u7#N8W>FEi@I|^k^HfhVDLH5F5zdD6dC|L|OU#_759_HUL%=_;`&o>X?!a)?8u#27Al398Gl^Q1(B z8JbJq(m#L`q_gt%BOd^$3vhddfRRWFC0KtzsnmQZ+HFEn+_ltC{(2JI17kKTg7&P} zo7S^@^@Xin*SjuaPb6#!`qnDxs!X*{k4d|F_B?gI@IeGE0wb;4;(NdKdDR4=f@ zYleH*-<9`c05F_tbHT$S+_$ + + + + + New Features :: inspectIT 1.5 + + +

+
+
+
+ +
+
+ +
+
+
+ +
+
+
+
+

New Features

+

You might ask yourself what the inspectIT guys did since the last release. Each version brings new features and bug fixes. The best resource for in-depth information is the inspectIT Releases page.

+
+
+ +

Release Notes & Upgrade Guides

+

If you are interested in a specific version of inspectIT, you can use following links for accessing all information related to release including the upgrade guides that can help in order to migrate to a newer version:

+ +
+
+
+ +
+
+ + \ No newline at end of file diff --git a/inspectIT/content/static/welcome.html b/inspectIT/content/static/welcome.html new file mode 100644 index 000000000..0b3f82115 --- /dev/null +++ b/inspectIT/content/static/welcome.html @@ -0,0 +1,83 @@ + + + + + + Welcome :: inspectIT 1.5 + + +
+
+
+
+ +
+
+ +
+
+
+ +
+
+
+
+ +

Welcome to the inspectIT

+

inspectIT is a free application performance management tool developed by the competence group APM of NovaTec Consulting GmbH.

+
+
+

Before you start

+ + + + + + + + + +
+ +

Documentation

+

inspectIT provides a comprehensive Documentation on installing, configuring and using inspectIT. Don't miss it out.

+
+ +

New features

+

Each new version of inspectIT brings new features. Don't miss any of them, check them out on the New Features page.

+
+ +

Join the community

+

On the NovaTec Blog page you can find interesting posts about inspectIT and Application Performance Management in general. Have a look and join the discussions. Go to NovaTec Blog .

+
+ +

How can we help you?

+

You have performance problems? You are not sure how inspectIT should be used? We can help. We are experts in performance and have acquired a substantial depth of knowledge through our work on numerous customer projects. Don't hesitate to contact NovaTec Consulting GmbH for professional support:

+ +
+
+
+
+ +
+
+ + \ No newline at end of file diff --git a/inspectIT/icons/eclipse/LICENSE-NOTICE.txt b/inspectIT/icons/eclipse/LICENSE-NOTICE.txt new file mode 100644 index 000000000..4f94768ea --- /dev/null +++ b/inspectIT/icons/eclipse/LICENSE-NOTICE.txt @@ -0,0 +1,237 @@ +%% This notice is provided with respect to Eclipse icons, which are +included in this folder. + +--- begin of LICENSE --- + +Eclipse Public License - v 1.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF + THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + + 1. DEFINITIONS + + "Contribution" means: + + a) in the case of the initial Contributor, the initial code and + documentation distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + + i) changes to the Program, and + + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and + are distributed by that particular Contributor. A Contribution + 'originates' from a Contributor if it was added to the Program by such + Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include additions to the Program which: (i) are + separate modules of software distributed in conjunction with the + Program under their own license agreement, and (ii) are not derivative + works of the Program. + + "Contributor" means any person or entity that distributes the Program. + + "Licensed Patents" mean patent claims licensable by a Contributor which + are necessarily infringed by the use or sale of its Contribution alone + or when combined with the Program. + + "Program" means the Contributions distributed in accordance with this + Agreement. + + "Recipient" means anyone who receives the Program under this Agreement, + including all Contributors. + + 2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare derivative works of, publicly display, + publicly perform, distribute and sublicense the Contribution of such + Contributor, if any, and such derivative works, in source code and + object code form. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, if + any, in source code and object code form. This patent license shall + apply to the combination of the Contribution and the Program if, at the + time the Contribution is added by the Contributor, such addition of the + Contribution causes such combination to be covered by the Licensed + Patents. The patent license shall not apply to any other combinations + which include the Contribution. No hardware per se is licensed + hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. Each + Contributor disclaims any liability to Recipient for claims brought by + any other entity based on infringement of intellectual property rights + or otherwise. As a condition to exercising the rights and licenses + granted hereunder, each Recipient hereby assumes sole responsibility to + secure any other intellectual property rights needed, if any. For + example, if a third party patent license is required to allow Recipient + to distribute the Program, it is Recipient's responsibility to acquire + that license before distributing the Program. + + d) Each Contributor represents that to its knowledge it has sufficient + copyright rights in its Contribution, if any, to grant the copyright + license set forth in this Agreement. + + 3. REQUIREMENTS + + A Contributor may choose to distribute the Program in object code form + under its own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + + b) its license agreement: + + i) effectively disclaims on behalf of all Contributors all warranties + and conditions, express and implied, including warranties or conditions + of title and non-infringement, and implied warranties or conditions of + merchantability and fitness for a particular purpose; + + ii) effectively excludes on behalf of all Contributors all liability + for damages, including direct, indirect, special, incidental and + consequential damages, such as lost profits; + + iii) states that any provisions which differ from this Agreement are + offered by that Contributor alone and not by any other party; and + + iv) states that source code for the Program is available from such + Contributor, and informs licensees how to obtain it in a reasonable + manner on or through a medium customarily used for software exchange. + + When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + + b) a copy of this Agreement must be included with each copy of the + Program. + + Contributors may not remove or alter any copyright notices contained + within the Program. + + Each Contributor must identify itself as the originator of its + Contribution, if any, in a manner that reasonably allows subsequent + Recipients to identify the originator of the Contribution. + + 4. COMMERCIAL DISTRIBUTION + + Commercial distributors of software may accept certain responsibilities + with respect to end users, business partners and the like. While this + license is intended to facilitate the commercial use of the Program, + the Contributor who includes the Program in a commercial product + offering should do so in a manner which does not create potential + liability for other Contributors. Therefore, if a Contributor includes + the Program in a commercial product offering, such Contributor + ("Commercial Contributor") hereby agrees to defend and indemnify every + other Contributor ("Indemnified Contributor") against any losses, + damages and costs (collectively "Losses") arising from claims, lawsuits + and other legal actions brought by a third party against the + Indemnified Contributor to the extent caused by the acts or omissions + of such Commercial Contributor in connection with its distribution of + the Program in a commercial product offering. The obligations in this + section do not apply to any claims or Losses relating to any actual or + alleged intellectual property infringement. In order to qualify, an + Indemnified Contributor must: a) promptly notify the Commercial + Contributor in writing of such claim, and b) allow the Commercial + Contributor to control, and cooperate with the Commercial Contributor + in, the defense and any related settlement negotiations. The + Indemnified Contributor may participate in any such claim at its own + expense. + + For example, a Contributor might include the Program in a commercial + product offering, Product X. That Contributor is then a Commercial + Contributor. If that Commercial Contributor then makes performance + claims, or offers warranties related to Product X, those performance + claims and warranties are such Commercial Contributor's responsibility + alone. Under this section, the Commercial Contributor would have to + defend claims against the other Contributors related to those + performance claims and warranties, and if a court requires any other + Contributor to pay any damages as a result, the Commercial Contributor + must pay those damages. + + 5. NO WARRANTY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS + PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY + WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR + FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible + for determining the appropriateness of using and distributing the + Program and assumes all risks associated with its exercise of rights + under this Agreement , including but not limited to the risks and costs + of program errors, compliance with applicable laws, damage to or loss + of data, programs or equipment, and unavailability or interruption of + operations. + + 6. DISCLAIMER OF LIABILITY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR + ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING + WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR + DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED + HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + 7. GENERAL + + If any provision of this Agreement is invalid or unenforceable under + applicable law, it shall not affect the validity or enforceability of + the remainder of the terms of this Agreement, and without further + action by the parties hereto, such provision shall be reformed to the + minimum extent necessary to make such provision valid and enforceable. + + If Recipient institutes patent litigation against any entity (including + a cross-claim or counterclaim in a lawsuit) alleging that the Program + itself (excluding combinations of the Program with other software or + hardware) infringes such Recipient's patent(s), then such Recipient's + rights granted under Section 2(b) shall terminate as of the date such + litigation is filed. + + All Recipient's rights under this Agreement shall terminate if it fails + to comply with any of the material terms or conditions of this + Agreement and does not cure such failure in a reasonable period of time + after becoming aware of such noncompliance. If all Recipient's rights + under this Agreement terminate, Recipient agrees to cease use and + distribution of the Program as soon as reasonably practicable. However, + Recipient's obligations under this Agreement and any licenses granted + by Recipient relating to the Program shall continue and survive. + + Everyone is permitted to copy and distribute copies of this Agreement, + but in order to avoid inconsistency the Agreement is copyrighted and + may only be modified in the following manner. The Agreement Steward + reserves the right to publish new versions (including revisions) of + this Agreement from time to time. No one other than the Agreement + Steward has the right to modify this Agreement. The Eclipse Foundation + is the initial Agreement Steward. The Eclipse Foundation may assign the + responsibility to serve as the Agreement Steward to a suitable separate + entity. Each new version of the Agreement will be given a + distinguishing version number. The Program (including Contributions) + may always be distributed subject to the version of the Agreement under + which it was received. In addition, after a new version of the + Agreement is published, Contributor may elect to distribute the Program + (including its Contributions) under the new version. Except as + expressly stated in Sections 2(a) and 2(b) above, Recipient receives no + rights or licenses to the intellectual property of any Contributor + under this Agreement, whether expressly, by implication, estoppel or + otherwise. All rights in the Program not expressly granted under this + Agreement are reserved. + + This Agreement is governed by the laws of the State of New York and the + intellectual property laws of the United States of America. No party to + this Agreement will bring a legal action under this Agreement more than + one year after the cause of action arose. Each party waives its rights + to a jury trial in any resulting litigation. + +--- end of LICENSE --- + +-------------------------------------------------------------------------------- + diff --git a/inspectIT/icons/eclipse/add_obj.gif b/inspectIT/icons/eclipse/add_obj.gif new file mode 100644 index 0000000000000000000000000000000000000000..252d7ebcb8c74d6e5de66ef0eb8856622a0e9d89 GIT binary patch literal 318 zcmZ?wbhEHb6krfwxXQp_S{7na6>d=(Ze15;Qy1mX8t2lT^ zHOHgY9FJamJZkN+sI|wVk6ueSb}ixP)r4bLfn?%^+sU^crQUj&`rv8W|Ns9PC;*B- zSr{1@v>9|jW`O*}z!rUAYJrE2RKG`JYNRKhwpQrnor{(Ph`sVEnyR2Kc;c~(o$WdP zMb0Wl6K^dQ-0hgs;jyv$?^#y0E>xthT(wy1lx)zrd5n>BPaU z#>BVF#iyOj>%zv#z{b$Z$FZr=?9$G*v)1p?($3P;&(hS^)z!|u+wj%d;Mv*Q#^3VC z;qBku(AwVM-QVHP;`8F-+tcOr;^XAu=;-Lx^Z)Pe>+iYlf`v30w|M2_&@%#Vu{Qvj;|M>m?`u_j>{{R2~{{R30A^8LW3IKlqEC2ui z01yBW000NmfD8^06BipEA0QzkCMhd_e=j~kLq|?hR#;tNWoIpr3=M==J3l`>Q6wcP zpMM-}ZA4FMZ*Xv8IItX$7;RNhY;s`&Kyq$3au|>jZ8l$XbHhM%b3$ejkP2-&Xmxhd zc6D4z3y=tHIb?Tu(sy@RNeGYzZ8BS7KtRNKc}7|XkO;J4(qv2m0Vww9StDXk0Z0Sd z@NqFEOqf1>_{^x$X8@!CAOpaNfn-UK6Exzm2mq-iq%>5Dc=7W`1%M1LbLO-Fpn}ey Qe<%Rx092=j2Y~KPJ&%Yigith%)uIVWkY^6As3FJHcV|ML0e&;PG~{(trB|Jz^x-~Iak z;rIWKzyE*v^Z)zb|3Ciz|Ml^O${&W6W z-q6_0A!=0cg29QUQ&=m4M~36D$0Sw%RRx^RZoRWDOJjUQRwNvBG<52dk=(Sjg;P|? zJ?qEkcFt+G%^ZSFhnttRP5iI(=jWoO{R_>RCP*-CIXr=Nfxy2f8O1LIf>b*AG#7qm z&}g!Ee5Vpr5*oP3BuI$C^uzP^Ij%kH^rAx4x)>KsQn~2xnT>POl3poG%ix5BX>y8H zDHB~(&oMG;%2_@#ek91)7}+(0Gi8~_H1XJbdpbopC-KTmWaGGE=r-ZCp_R(f8H}l1 zf+FTzTR9XyG|Sl^_*^56R}5 literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/agent_na.gif b/inspectIT/icons/eclipse/agent_na.gif new file mode 100644 index 0000000000000000000000000000000000000000..8f304545e3c80ae5db1d5afd0bcf9b597ad25b29 GIT binary patch literal 1085 zcmZ?wbhEHb6k!lxc;3ozKtQ3=-yt#5Q6p(uT8#Y(2aB8pyOI>^69Eo2nHDcR0_=*G zH|LqQb0GPIbB8BT}3(mwd*I98T1ro zT}UYCEG-Ud*f_0Hzq_m`tZ8#p>z3H|ZHZkwnmRjDdUvMw?n>|9lhMDsv1it&g6!-G zd$K3)ozvNrJ89p%uEyNS`=aKY>6x~;ciNIkvlh;tF?-&u84Kn%d}(c5GOuFA{K5qb zr!QSHb^WrEjmt}K%$Tug)z-XikG8F8Xx)6Vbm!wOn`YJReY$7+l=a&WZ{4w_{m}Ex zI}iR?v0~S*4L?@T-MeSawmnBCAAk8{!-^vZ*B(2v{q)i0bI-g!bnN7TV;4>x-?;MP zn@i`mZNB#Y;-zz!E}y@2<=WM&=l0(IaP{Vcn>TMAyZ`C|Nj5+_y4bd|9}7c4+IROfW9H1_)pNeC^fMpHASI3vm`^o z-P1RKLGdRGBNxMe1|5)FKpBOB;~&F+&OhH34zX}c*>y-bE;_;~s+YhcW0Bk;sN%CM z;o+myo|&e(DLx`U7!DoOv+odKEMj%#5?69>nqjQ&DQZ#r=7!_pX0?uq|INQ)sp^wVqBEmLv!&ggHxG-ao|iKWlw$BTSl&L%u5R78 zm!F?{GA(HN)MgpH#N(2E{j>&41%r;}!|lxiUYipfx*ZxG&EQCJP+-!UVW_My<>{H3 zj?FAL4;Xedq-y8{Ip6eI>?W+~6n1Bauyex!CM%H_*8&|LNb7o?5cu_7iGjV5Az(Ed nLrFp#gTz0^1&@~O5`K1;HI74I5p%=)`_^nJ@-G~n92l$t=;MR+ literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/agent_not_sending.gif b/inspectIT/icons/eclipse/agent_not_sending.gif new file mode 100644 index 0000000000000000000000000000000000000000..e6e9ab7531f23970ea09889f262d7617c91b5dfd GIT binary patch literal 1092 zcmZ?wbhEHb6k!lxc;3Rm5IU{W-yt#5Q6p(uT8w>8f?Y|9wMl+|O{Rri(Q=dW)y;XP zEqSIcWvkrESG!fLZpzE^s9e)iWZYeB>|MRKvoNQtD7&jD$G>*{q%wn^qO8u+;-H3& z(<=45%ZkF9Hb=E?iEZDO*tMgnvm>Q(z$J$uf4x`>D;Bu=PzBkcJ=DHy|+JHz4_qg z&0DAMzB+dQ)A5II@87*}>)wOA_aB~n^!d@F+n1kydGz?n!^h8`J$v@_#j6)DUR?eB z|NXnyH@^J8`}O~)PoKVg`SSF~|L;Kzd|KI)k|Ka!lkH7za z0-`_vzx?_C{qO%DfB*mb_y705|9}7e2LgssK=%+({3qyKl$uzQnxasiS(2gP?&%xA zp!k!8k&EF!gAT|!pp3%6@sHs@=b!5j99g&|ENU18ome`BwIXlEMjvShpyj`wb@w87zLIaGVD(j;_>)++<%r}Jr_rafVx0~Lz4(k z)2FB23mh6!E^xRwC@`2;^L_ZpoFLF6;XdVqgQ2q@H{cU%o#}9ArsIP-&i7_Jo$EHeGQsly|Njg`0L7myj0_CM3_2jSAU`p%B_Ehs;GrYc zf8ycmsVav!SR#s_DRHp0giSK#NHAzP`f5gy1mFE;$Awy_gjy@yg!L5yj!a&1@Ogz4 x53i8AtC}#MK&Jq|u%WxEh@h;zps1>+vY522m`bpsxVTcVlY+vM6)PPXtN~eZh|2%~ literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/all_instances.gif b/inspectIT/icons/eclipse/all_instances.gif new file mode 100644 index 0000000000000000000000000000000000000000..bdef8792849abadfa4263860e2bac8d46b99c1b8 GIT binary patch literal 97 zcmZ?wbhEHb6krfwSj51P+q5RTVP#?ahSHv`la?HYo4q|117vVPs%nXV75) z0+3n;X0?i4cm5gda(K$6B6Ik(fP-^pm}Z#jvcfj@c}q0=m+dNzKXIWbUX8&T0Oidj Ap#T5? literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/buffer_clear.gif b/inspectIT/icons/eclipse/buffer_clear.gif new file mode 100644 index 0000000000000000000000000000000000000000..3fdf778761201c4d5360f8613e1694cab49ea74f GIT binary patch literal 392 zcmV;30eAjKNk%w1VGsZi0Hr?wJVjY3Mzb+Xuue`+Pft%!P*71(QBqP;Q&UqtQmR!| zRaseCOj@K}U0q&YUSnfpWMpJrW1d}OpKfk$ZflrtZ*OgEm~nA&a&mHWb8~cbbbNG@ zeRPw4etv&{e~EjJet?jYfQp8OhKGlTl9sEHmbsgtz?`7JoS?v)p}?G>z@w_Vs;|ka zuga^h$*Zr*y};GIz}CIN)xE*i(9+`2)8Ww5;^5!o;^gh(h%Br z{{R30A^s6Va%Ew3Wn>_CX>@2HM@dak03rDV0SW*=04x9i000mG5C8xMkiegCNGuwU z$Rx28YC50J((lvrS+7{$>ht6HOevLVoI;sTq|MLbd%apOmzx!Tktn~2PX>#{W-u}_ zf-oc!KUh~bG&YSjBn%i288;XW7(O~VI6F8vBnb{98aEjt4n82Ns{jHR3my&~3zz}6 mw*e(L3J@3&2scR>2NMMo1{g^N6&RHh1i}N97X=qdK>#~3NT%li literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/buffer_copy.gif b/inspectIT/icons/eclipse/buffer_copy.gif new file mode 100644 index 0000000000000000000000000000000000000000..516c5d80e0957b69d1cd4ac502479bae8dc4fd69 GIT binary patch literal 601 zcmZ?wbhEHb6krfwIOfe@;~J^qw%O2ogKf|%pQt6V$@BBFXXNJ0XsYmOs_<>D@M*5_ zZL0LCDVbDTGP$$btFzj>v&MU3y+>ztUtPn5na!>TE$=gRy-aUTu_VJTjj_s?H{{{R2aFt~x@ zKSAfB)Wnk16ovB4k_-iRPu~Cr#h)yUTnzOLIv}@$B8q{1VMBdWb4zPmdq-!BvUqP_ zf1h%_u$|Ym=`*Y~h3omeRMa(87OSf*Q&r-vXZO<8)z;Nnr?pX6iLHK`fu6p;p`L;M zK|Li_#pBbAj7?09P0yPsu_$=T$;*0~Sy`ByS(=$CF-v*Kcu325iM)Oz!oVcqCh0CA zDdENV^B1GCn6s#B2gk+<9!>(B_6>g~OyFQ@VYjLJ(lOyg5ECnhn1P1~2ZJ>Ltvk@S literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/build.gif b/inspectIT/icons/eclipse/build.gif new file mode 100644 index 0000000000000000000000000000000000000000..3191d8abdc806ef772b20051cb29e5f888b980cf GIT binary patch literal 349 zcmZ?wbhEHb6krfwSZc&z6VhT6(rOdhY8&2eAKu{*(d`u7>m1wX8aL53exh4^zgxm2 zkEAJ{$y2>kXZWPeh_6}`U%fuJdw*`v{({~Eg}ny~`VN#&*k3yFQ0c_O#gh(|PC8UE z$r?6``Y(@0DoD&QMZTP*hN1^O2G3l9y%m5s;P=kdR~v5)fe$6Jrjt7Z4H_VwyLf Nfst|PDrZLqYXH1aeH{P* literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/business.gif b/inspectIT/icons/eclipse/business.gif new file mode 100644 index 0000000000000000000000000000000000000000..6749b0939bde1bb61683a65a7686337e19f14a25 GIT binary patch literal 379 zcmZ?wbhEHb6krfwSZd3lCc&p7$*&?QpeD(uCdscRDWD=Hq$VY(CMBdIBcdWFo2o68 zrY)4NE0m!tl%*$#ADs zp;7Ot(d?ts?5ooqV=^Pxe?}e<1w`nf9voiA00WBu1f7dg z6H8K46v{J8G8EiBeFGR2f3h%gG1xQcfK-7z%D~oqV19vzj#R%#BHxl9PbTe;c|P;A zP470|6j literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/calendar.gif b/inspectIT/icons/eclipse/calendar.gif new file mode 100644 index 0000000000000000000000000000000000000000..e4449be0a47260f141ffa8a0e9e95f10b8ff552c GIT binary patch literal 420 zcmZ?wbhEHb6krfwSZc*!;Sgcrkl^4E5fBid9oDN6(V-jOryJ3yA2~rkYJySpq=*O) zqnJsi@lz}kr&}gYPe^dcNU*R;nPr z)VFx4f5|fclH~!V%iZcWbxc^%F=NGq84E&}+@3LGLCo?y3l_{sTXS#4iV20AA8y#t zQL*De-L9uS2VcxQ{%YB|cN?#MxNzad?wg-3+_-V*?w1!oeth`xZN1pQUqA zYGO%hib8p2Nrr;Er*D8lT2X$kf@^Y4WCRV9%ffG8Pmd3~Vh2<`;PANcFoNVmz2(p(etecKIid^U~m~r&R}&jAorR zR*?|uI_4a-F~vmY!iPzxH@+&b{%f@U`k#twXF)Af)^=7?t!^zrXMs{yU0v2vjZy(; zZVQV^Q|4&Q;dW+LYwIvoTcyVA%w%qEykV2M$_6H9h7HOal=g05aArJwqC;nAh0e|nn~@bgFF$yC zdfbA-z^PFo)1s4>Bp0qLDA`b$y)r##Wk%JC?1jhL{{R2afEy_OWMO1r5M$5*DFNBZ z!0NX^m1$9uLDH%y#s(MBk~KLA?9E?%nCEU|WnUWjq1T1m=}?ABq>o10Yth+jS!ZRN8HElbXK{oa)3vQ)nxB2_k@@jtN~w(Lr(wz literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/catalog.gif b/inspectIT/icons/eclipse/catalog.gif new file mode 100644 index 0000000000000000000000000000000000000000..33af4b7b4f56f2f679864fb770340f5111b95e91 GIT binary patch literal 650 zcmZ?wbhEHb6krfwIOfbCZWNnn$E)GCS=V!;sqZ?wpw%hSS{~ucf?^lPCeMpao^MyQ zJR@^fYqm{s{x>^ssZ9VX=_29dyx%1x7Ll1u+eDv$Yv)?D4{rUK0-uah*f4uIz^5)-_H~+7``FHir{~PcAfByFC z%eS8|fBygc{pZ_X|3Ciz|MmBOAYd4pK=GfIb5UwyNotBhd1gt5g1e`0fI?bPey)OR za!zJJajHUcer|4lo~b_|Yc>%O?3l!0$YpI~ZD-AC#BaQ8 zzCP2AolFLNPHbJ1wRCm0wRN<0^?3FzchJz#yr_9WgKNtH2357&s<%{BIktA$D=R9> zD=Ek;DzKaK-jI`%mHqrlj@6WLxvk8<|I&X1FSPqQxA90AWE?0cIIufdOv`0WK%-MD VwRsX(Ohwq+z^!{nkw1lu( zDPc2HV&`P7KEHX-jYA>R6T@ewM9fTyo0E0x)!k_2wz@P-Sk|APSoeSzXn7Dfh!ClGm2|QYKe-7h)T#Q@$He9j?`9AQ4ux} zP&?5iq-<&#X{ezXX|J!t&@97aWMj6{+|Gn&T@xp-YRQ`y8=a5yFmmdtZYWsP+AXH8 bqq4{4!~q5-W`Qq@Pi$O#yuX);k--`O+yTtY literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/collapseall.gif b/inspectIT/icons/eclipse/collapseall.gif new file mode 100644 index 0000000000000000000000000000000000000000..a2d80a9044f38833cb728a69c88294ce3fd007c7 GIT binary patch literal 157 zcmZ?wbhEHb6krfw*v!DtJ#F>UjfWZCs($|cfA#bKkH7!F`St(Z@BiQa{{Qv=|DXRL zz<>l4f3h$#FmN;IfW$y%FtB(Pob+71*X+evXI>YLE;&}Fj8#mRE%&W?B30shyu13% zpT6C#3k-fJGjKF52@24V6I?%GvcZa|)%y<^9(-F=IB9W`k6g3(YLhfsMh0sDZC^x! literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/complete_status.gif b/inspectIT/icons/eclipse/complete_status.gif new file mode 100644 index 0000000000000000000000000000000000000000..23c97f09e677bb8812b26d4415fd97997e5c95a4 GIT binary patch literal 76 zcmZ?wbhEHb6krfwn8?f!yfVYEKB{JS)&Kwh6@RiYGB7YR=r8~QNS=X7c257w({Jy) dG&pl*gzr#W|He>ggQJQAZ^o%@>w*~=tO0mX7=r)+ literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/dates.gif b/inspectIT/icons/eclipse/dates.gif new file mode 100644 index 0000000000000000000000000000000000000000..96aae05cbf44981945d9bb16620e4641cb105610 GIT binary patch literal 181 zcmZ?wbhEHb6krfw*vtR~4fgi)?d$j3$Jg7}&$pkyfByXX{rl(NzrX+g|Nryn&u?gG zh>wp4ssnO?q~cE&Mg|5x1|5)AkQodtNf%CPp5N7;wMFFDsiurcGgQu28a!DzM`KUQ zl*(o5hXiWQ^RIBu3)hh1c5rrH@%d*1XKIpLL*zoOFwqGN3{0H|+Yd|R`ZGArIJnI6 eT+^XUhpKX6#{3;|ThIMuZft5SZEY1~um%88I!L|% literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/debugt_obj.gif b/inspectIT/icons/eclipse/debugt_obj.gif new file mode 100644 index 0000000000000000000000000000000000000000..c1e4ee3a59d2d69570c2023bf953fcb8b5cf3158 GIT binary patch literal 243 zcmZ?wbhEHb6krfwIKsftv-Hy8b9dM5K9|zAjKQy$!6uI(bUK4y4})9n|Nry<-;duv z-~Rva5C8Yu&+09{Ki@vjSM|`AC37;uLM#ma|NqZG2q^w!VPs$sVbB371lh^JTA-lX zmlDb4Z0I#PM2W$OMTEuoWJ>=L#-0hSNjn;PcsvBXAI`84(G^a;l;I;0@>=UumWgcG zbl&CNEb=cFX;_4aoSoc$ZpR;|Se@w_)la>5$0sUzusLvN@MkG8u(LRr7g;kh8*r#r PDzh>(1qc>({TZtgK8=Pxtin1PU@>0L7my zj0_B%3_2hgkQodtJ_aW}SFcr@;eCfEY&nSJI{rI!5RRgR5Qr{ literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/defaultview_misc.gif b/inspectIT/icons/eclipse/defaultview_misc.gif new file mode 100644 index 0000000000000000000000000000000000000000..55ca8f3a9bfca92ec765a53d8531f8f64a425eb8 GIT binary patch literal 351 zcmV-l0igazNk%w1VGsZi0M!5hXm*lsdX8>-jw@T4DR0CnZNVvS!76OMDr>wcYPTwC zxG86{Drd4PWUMMD`~CmB-TKDl{m|+D zfxhjB!S9d8@R`f=q|)`T*Z0`&|M>j>`u+d+{QvX&|Lpny@B07M^Z)Dl|L*$#`2GLs z`Ty+t|Lgky@B9Dr{Qvj;|NH*``u_idhJk{GfR2rNjf{GjlX9S$Y^R)P|Ns9000000 z00000A^8LW002AyEC2ui01yBW000JjK%Q_&EEVab=`lFvmYpNmUxXz4%DF}bf}d|cDunVH>FGrOY- z+D{EEKZPcINzVS9ob?$uHn=&y~n$8D`1 z2l;<(sQc1T_xQ+>|Ns9p5DgT6vM@3*7&7R9bb$QCz*ci$MuCTpRKNR-^bncK1eb^v zqDfrti^MpzbSB1VUt0b}aql73M;xvKD|}WtK1fk5iV!?;_@hVR1O_f%UNKGv4?_+f zX(=fl4i6boi78XK>s@7pr?3jKN=S;#aFP{YATA>-vrJN4*4aRuZH4n{cJUaejfRzyGg({{Q;t|Bt`_zxnn5-S7XO|NQ^<_y4c||Ns04 z0R|+X_>+Z^fq|Pr2P6hEgMr1d;H2m3y<#uUnzA}+^c_&=$(S`wIAV{%8LtJ2_rLFH qaC!gR>O!K23L9(3lAuZ+i}~l1lf0J7iQdRDyYVt<-ve(J25SJ2Iz)f~ literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/discovery.gif b/inspectIT/icons/eclipse/discovery.gif new file mode 100644 index 0000000000000000000000000000000000000000..ec6cca4525bf483077bcc2039eaede6e71258c89 GIT binary patch literal 362 zcmZ?wbhEHb6krfwxXQo~6H^{jeln(fZOo*T0n4t1_pLGO*y1x`Q}l#2#d9{*&R!j} z=48ytlQC=8PC9vVEf75cDvl|iRDSZw$w?>6*Pfg-V@=K4ljW1v0+qx}ngk@vPoA7q zUJhiJLo_{ka&qV9;sbl<|Nryp$;q{o)ztp~|6g8S4kQ^U28usf7#SEe8FWA1TtSGIUkiPz+c zz{xe?g-r{l)W_zwtex7Ju(mO%Y2vDeDO(z+Y-yjecI$-n?rE!frmdPZYgymy4U=Xs z+cPa|-;C^;^A{eSm3VM=&XIZf3zy89zWl(9(Ixe}Db{=f}_AKYsrC z_51JdKY#!J{l_r)fZ{((=c3falGGH1^30M91$R&10EM)o{9Fas%0H} literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/exceptiontracer.gif b/inspectIT/icons/eclipse/exceptiontracer.gif new file mode 100644 index 0000000000000000000000000000000000000000..58873a11668491c5fef590a56a35b668ab589cd1 GIT binary patch literal 601 zcmZ?wbhEHb6krfwc*ekRP*ifisKkH&p#KTU|H~_`ib-D)lfEOR@ZUfDe{j@C|Iq)T zQ6GcDKZQpAk4^laSNzh{?tg6B|ExlBqu51j_t_OK-*o)W@4x@IUwZQB?U#?g{(t%X z|LxEJAAbG+_UHf4zyDwU`2XhT|M$QCfBOCZ`H%lEe*Ay+^Z)Cg|KI)k|MB<#XF!pk z|6l(6|K`{Kx4-^>0IC7X{`&v@@BiQb{(t)O|JT3&PamDHE(w@e@6l81HNDAgafj2q zR+k4S`nOE5-PmWhw#Q**m*cIyHK%7eJv*`XtiIv}8@>M%JI^Z0{Ljz%pPT!C>C*oz zR{RG8hQS9Ef3h$#F!(X(fSd-369)Ee4Sr3{Ev>C?t{!e}tsdTn1jSA0a*;epY#XB_&y#W>y|%79Lg&z4sa#RxJYT3<5?nih7E=iWV(8 zDGL-HyYvXF`$(J#T*1;Iq#dz=!Es6Z1jQDugc%NtlsP5D48?*RH!bPv?#WvbQ|Y{9 IvIB!P0AcIn4FCWD literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/exceptiontree.gif b/inspectIT/icons/eclipse/exceptiontree.gif new file mode 100644 index 0000000000000000000000000000000000000000..234486172ca3778924ccb8815871b43a86908777 GIT binary patch literal 101 zcmZ?wbhEHb6krfwSj4~(Q?)9gZf#og#)(Uh=X7l^ny{;E%D(^q|117vVPs%nXV75) z0+3n;X03`{cm5ezIXq=zmO0EV=;)j|&1o9DrI+%u+JzZm7b91hTuEb(dA>qHg^|G; E05nA-vj6}9 literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/export.gif b/inspectIT/icons/eclipse/export.gif new file mode 100644 index 0000000000000000000000000000000000000000..5a0837d1e475ec48731c88f6d554a37750df4693 GIT binary patch literal 329 zcmZ?wbhEHb6krfwxXQp_Z$IBYe!fro^uXMCfjM(S@)ri=&J8YD7??XZC~s~=$&!eY z#Sx{8BTE;@R4k9LS)EY3I=*^Ea{bzr`n73I>suF|ZCiMD?&fRpK=ImD$qj2;7oBYb zp)(U!U0S~T*77~K)*rlUT(HErV6j8_a^9F(|NsAIpdL{C$->CMpvIsBG7{t`2DUVZ z$ps!dQvDvvnIT%LohlPQYwEbJT&CH{W#Fcfkd(lo*X}eWaeB?N3@5410-q@!PLq$b ze>&4(z$eb<-^j-+$H~RR-P7AE$IUJyBP}B~Ra#nBMsx}*6B`>d2iuZ`94lB>M7TII GSOWlifno0e literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/filter_ps.gif b/inspectIT/icons/eclipse/filter_ps.gif new file mode 100644 index 0000000000000000000000000000000000000000..3d061d77cbe83a329576fa10e54a3ffafae94b98 GIT binary patch literal 211 zcmZ?wbhEHb6krfwIKsdnt7_rlA7^YA8W2}*A68(PHqAV~#jk&_wyDLxKQI2gUH|9p z#y_vu|Grc6=k><<#lln41*fD5c0}?0|NozX2%z|rg^__lm_Y}m7Gx&_Yrq4QzLZS1 z#|hIIl~z3slyFJd{XuKnq6PgLf(f50{&@AbTnxz(6FwE>vM|Cxg1hxGm!{{w1kc5a gmR(1lUe5S&^`qLp8*9pDKe?l~+j50|D+7Zy054ohcK`qY literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/flag.gif b/inspectIT/icons/eclipse/flag.gif new file mode 100644 index 0000000000000000000000000000000000000000..8190ec3b03dc62338bb5f7894920674606afb3a0 GIT binary patch literal 321 zcmZ?wbhEHb6krfwSgOvD8E0OQU|ODHT#;g2lWyFcY0#E!(4KA3ldm_iP;Y9H?$l!4 z>BTzp%eALg8P9IATh`~lX-@Kv`BrNd=5Jh9wtaQYzSUJb*ESs5(sgu4|A`&_r}s>| zaB$wgr&|ACI{ts@@&9f7zxNscKehh*()aKC!v8;3|NnWIfqFpkpP+M5YGO%hib8p2 zNrr;Er*8m*;!hSvE(UuB9gsaBk20`%9++R?p(E9Q+_Sk^Cyb{zW6R8iCTsm|}rjV$2k?Ju!`<48{TR74ps--q;9nyG=S9 PCnBkRILAtxlffDQ(XA%5 literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/gel_sc_obj.gif b/inspectIT/icons/eclipse/gel_sc_obj.gif new file mode 100644 index 0000000000000000000000000000000000000000..363ffaf6f0b07642d5f60bb3c0df0a6ec5c218d2 GIT binary patch literal 380 zcmV-?0fYWWNk%w1VGsZi0M!5hKR=&8KVP4p&k;S9E?BNVU!R|^&n{)IKYza*WuhZ$ zr6XyiD{!bRaH%(Utv7hBLx8eRh__FNwpx$8W0k*TmA`75!Y*;5T9CUsdYw3Un4P`O z^ZWmjuDjLq|M>m?KcBzz{Qr`!w5P+#Kfk{}pPzrfznHkVzu(`#-@kvqU!T8!f1f|U z&!3;4KYxEeUtd4p-=CkKf4{$<&(E*l-@mV~pYQMAt)^SGv5w!rpYPw#fB*ph|NmcK zU;qFAA^8LW002J#EC2ui01yBW000J=z@KnvDH@MMVsFXpc`QjGWhojAF3w8yhv|(5 zr_Vs-nFwn$6H3ynS`XyNuVymkZjd&Z6LLc6F)%ed4qjI$V;&D_Jvb{h4NDyZ1R72Z zCMhX8DlG~gprN1(Aet;JGzqG!tRN&LB`+>BHU=94Q8O7N2r#}r83i&?H5niv89W6U a838q0#mvkF&Rb_Q0096p&(_}7K>#~od8qRM literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/graph_bar.gif b/inspectIT/icons/eclipse/graph_bar.gif new file mode 100644 index 0000000000000000000000000000000000000000..90db63d382b6e1e62fcff7e56bd87e881c2a8ed1 GIT binary patch literal 401 zcmV;C0dD?BNk%w1VGsZi0Hr$s%TGYiR!GoRN!DIc*I!fJXIv?kPd2@S~tG$vbewnTCfP2H1EyI^B z^o4=QoH5CqG4+jz&Y?Bbq&d{3IQEy5*{M9*sXX4SKj5xFhQH|drJne+tDVd2{>4=K zz_&5@>&Hw-a0000000000 z00000A^ti;a%Ew3Wn>_CX>@2HM@dakAZBuJZ6HNsY-wIE&w6<1OW;FKL9KM0000G01yBG25`Wia7Zi?Pe`NEq%$dvr5x#5 z6yZWYj+P3#fwD9X)nn`YGO-L*Gu!+qt;!EWFJ5Gq-$KRsS!xzV6E71#1}OzU5ik-( v5H1iu1S11K3M&mo4=fKq0UrWC2q+0f4yQi=sy_#0pZ)|J2xxHn= z;a4|zw0>H*p#R9L2}fQ|IP&V=j@ny0+wbn^y}zUX?#`auyE^XdYWuNa#m@~Z9`2s9 z`|#m=`}&^jn>qLNo4Kdo&O7~P-s!hb4tD<#=Vcq5T|4ygUa{};Rezu5ER)wu`n zetv&_p3X1Zx^cWO>vM_Qn)HCRS zYym|m1N-rY`lewGDVN$P==zGrB&rdNWAkg1ayU5T+@im_^v zw``QSd7ZwMvdp5p(@=n__51(&{r~9j_4NDy_x%6u^7Z!o|LpSh`ThU={{Qv-|M>m? z_x=C-{{Qy<|NsC0A^8LW0018VEC2ui01yBW000Gm;3tk`X+Do(pr)8Mgb)zTT0U^W z?HoWqBm<#9IH-g~!w8q*Bn*q1fs<(fKMz8r<6!JkoDSh?xL`1m9m0T!L@LTpc|+%{ z6&U&@o~2NBDm#AzS2%uu4h|L`78F(^LK+$zDjF6V1aU4f5IhhQKQ{(25)w2vEhs81 JEv+RX06QZIfSmvU literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/home_nav.gif b/inspectIT/icons/eclipse/home_nav.gif new file mode 100644 index 0000000000000000000000000000000000000000..4472e8ce5b377934abe5910749b8c58591947424 GIT binary patch literal 582 zcmZ?wbhEHb6krfwc*ek>=hf;IH{B?(Il6LneC_)9y7e)Ys}kzgCpT_NZrqsBx;3M1 zYfjfr^Oy#Un1=ILZ(q4_=gaqB7ao7P|N7gr_usES|9bEBw__J?ox6Jb!jmu8UwnP~ z{`vgb@~XsXFsG`()# z)P`jC{<4g|#s1N-6zpQh%P)>bdmCa1Pm6E7bp=kA^+FJoIr3lG~V zK1Oy9RsvS`)>ACq4Fq%r%-n6-%-!@?>#ouFG;c9<)mp7~V2zfGUbChqzqhxVj+(c! zgob7dgSf1qs*0+ttT;m}qx_@Cj~QFpq($VE-iU}uGq>`{h>0ocD2NHm@U(JENoiR` mTx3|l#@Z^BazerJ04F22v4Td(0)|Fr1|zv05e@+g4Aub9HpVdk literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/ihigh_obj.gif b/inspectIT/icons/eclipse/ihigh_obj.gif new file mode 100644 index 0000000000000000000000000000000000000000..f99bdc358ff79b46f65a5765fc21d7df14019dd7 GIT binary patch literal 202 zcmZ?wbhEHb6krfwIKsg2rK0+Ty8f56{Lkt6pCgh#g(tsUvg(AS)~BPVzFfHS<@@iG z?snfM^_{e`_}0<(t-byK|Nji60L7myj0`}lbU>Owb~3QKE2#8&t?bcQrEt1w&dOZ| zXPAyJ+GNw$JDHbZLE&LdrY6S)62UTDEzHJC4Yu$EzjFNbx=OJ#?9uAtFPoM$&%7O* N8=rFdyEGGnH2@BJMc4oU literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/import.gif b/inspectIT/icons/eclipse/import.gif new file mode 100644 index 0000000000000000000000000000000000000000..d38085ad9c273000d1c7ef3ea0144de87b776e46 GIT binary patch literal 327 zcmZ?wbhEHb6krfwxXQp_Z$IBYetux?yuh5fA^8gfa_0sYEDX$@8&R?(qGWMI>Eg)J z#W5Aj<7-wY)UHmhUz<|DHmzxW>%y~b3(wBod@a6qb$sorkr;FE?8n*u-Kt|Id9CY|Ns9p&;Tg@WMO1rP-f5pnGNz216#_0i3J`yQvJsm zgg7JQrk+bO_7dTAKGtDoB+_bkXzjJf5992w1-8iStkCP%_Lx#5y6_`@wFT09~= wevLfb(wrPzoISn09c+@45|YAGBqXFHg(kDGv$8VHpTB^88S}Cb7e@wb0E(nwr~m)} literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/info_obj.gif b/inspectIT/icons/eclipse/info_obj.gif new file mode 100644 index 0000000000000000000000000000000000000000..2da001e3e98d5a9508b243840eb34d6bd5f5240e GIT binary patch literal 121 zcmZ?wbhEHb6krfw*v!Dd(-tpUwRPK`Bb#>~ zZ0wnJ>)xZs&tJcM^Zwq$C+|Ldx_kfOr7PEd{`&ps@srMpGbYVic=`IRL&r`&e*Wst zgU9baet!Ap-P0GZ?mT>Q>)wMe-@fnOf8g`iuiw6ZZ|dyWwRhLn9b2YMpK$5&`HPp% zEm$~x*RBm$ubzAK==R<{Yqo5fwRFkU1#=trY@c%K_{Jj#*REeyvSePxrE}Yk9oc^W z?uCuZOIOSCMOoj!eC{gB?kdXZEX?UHE9xvQ zZpzF1|NsBLfB$~}{{7|4mrtKQy?F8B*|TRrL59Hr6o0ZXGB5-&=zyF9iW3I*`i7vU z=9bnLb5mPeE1R}vBa=XXKR-+BK4XgjpE*9hc4lpcjKWKoDeLRD8i**YTd$;}*QzBb zy?uv(rgp2k5Z{4=GHM#Fs*3Wwr+6i0Rr*9F#ChbT_{Hq|g4kKPI61f(TpVsOv9K|F c2RS)9-)eJnbba6I?&$HQ#nbC&TO)%t0Q2DGu>b%7 literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/label.gif b/inspectIT/icons/eclipse/label.gif new file mode 100644 index 0000000000000000000000000000000000000000..ff946b6fddc4e38bd25d7472ba32abc531efc854 GIT binary patch literal 633 zcmZ?wbhEHb6krfwIK}`1o}PMn-dssZ7QHb%eK9<}u{?dTJpHje6XSWOCU8$p=AV(m zKPyvccDB$Q5E7mTBy)rp6o@V_5uaQozr0jtYL&wBa>>=@l3S|8nwvA%)yb`EP*~fj zy17ARQ=?pGXU5J}g#(=mySvo)_oyB1Q#&w0>%at^gA;WQP1HF$QFH$!!^2aIj!rc> zI^Fcx4AY$pgLW(m+PMfwhVBHSC80;xMIT+4dUQ+T@hw@WfM{p_g`I^LcjsT;TX=bI z$>qIeSN4`&++TM2XyesmRacKU-9A=x<81H!GrhOY^_{wL==O~zZ*I)FcYD>-Tg#t4 zIrQ$?*4Hmj|Ns9V?E3*G6#rQ|7o{eaq^2m8XO?6rxO@5rD5MqT=PI}+=VTTXrz#}p z=jP_;DP$%CiPW6b+|<01VugaD{46~N#h)yUTnzOLIv`JgB9wuBW`hqSvtoT)dq*3y zj}LQae_K=QgbqJOMQ?dM1O2)440Ls*6$8Yq?d>Zq(9Bt*8gP6>nRkYN1scUFx zddey?i&}cA`Uamo>8l+mQ?JIr;GuBpnv%any|TEgi^z#A!9g9MDE(u+*BjV_~=%ZT_k8jC3wI%Dq&cchk^Dpl$yu7#M^4_v5 zd&@5FFFSA{;p*|G8)tj(pXt4IuJ6>1L$_}%dGH|N&5b$tZm)WJYx%P$hu%Hg`ugSR z|Ns9(d^~ta#ebI0MX8A;sVNHOnI#zt?w-B@3TZ|8xeBhyIhh5;sS3&Yxw-jy3Yp13 zA~h#9H#M)MSfQXOKTD56@h1x-7ehUR4#+>C2xVYj(%{a_rdZ$B-qFV9?#|ZP-`3PR zp~H(=(M?`gUvKU_eI0FSMIUiXYa1JT8(VvOJ1aRhKNd3wWlgnRYU=75F0zVjV&<+Y zp8h9KdTRN~)T=TuI4hjGrsOSAf6f2IHBU(=kv6HP{zAfnf&wpvE%={!NS!h?`etNo y!YBI2xg);P?6DqCBjBz!Q*2N4AuYwZ^yR) literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/label_delete.gif b/inspectIT/icons/eclipse/label_delete.gif new file mode 100644 index 0000000000000000000000000000000000000000..ffe6c4fa90f29c1f578c81bd5257d47644fae7cd GIT binary patch literal 657 zcmZ?wbhEHb6krfwIK}`1M}<|7N~j-|R6i!Eaa2M3n1YU{r`~Z*gFJ7prv{eCZGp%t zDaoQYhNmxvr#F_TFP5i2mgj`K{YiJblkWBt<9R>Y2Te`pKN;XWD^uu`N5q_Lp*h*Y zb8>_i6o^i)l3!jbv%Fk#b-Cn~)RN}r%yW5h>+0k_Cugl|Q23mZy|z(xbA!sJM!C+; zjGe6tySvms7nOW2F5TawcC=6Jzyz%W6Lbzv)HyU!=jcSu{gVt2Pcb?=)#T`O(_=GC zcPt9pxhQDIqR^d7LXWPCKDs6G_?E0wTe2?fEWEfo|MK3#%X>>M?=8Esx9sBnva83N zZk+ACf2Q}=xxQ034&A=7Hq)#gM);AgyKI-=c3fa zlGGH1^30M91$R&10EM)o{9Fas~CvoozM}=t{0?ZV`n>eo}IOo zs$RIFn}?^TkEfT9kGH!vZzQL)uYrZ>E>klz^8igfUImvx8|E-` v2rAhWC>%T_&%$M<;oxvmfRkCAB}T(xky0bKtY?fwz(kjZ3F_KxObpflu`$`W literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/locate_in_hierarchy.gif b/inspectIT/icons/eclipse/locate_in_hierarchy.gif new file mode 100644 index 0000000000000000000000000000000000000000..1221750a819d1e4d589bcd1dd7ca1ed7916f43ee GIT binary patch literal 540 zcmZ?wbhEHb6krfwIOfIR8xCzLHT06yYVr*4SZ%|LJ=cK5Z)}`)KVyhbZT&IQBHB9uKlI%4#IdVc# z;MBx`sp&yelNwql10!wvtq+$=S0knZSS0!I*($y`{-Pgp`?NYBRrayse=EP<#Z3|CZ=_Up)h8gTa9Gb>@ z%+1TXtm-S8ut=EOFzpq05HVGi+UwTDE!S;dZ)9N7 fKa)*T*v!?McRQQ`6h*Ie$~RN>oE<=s*3y|P@nuhw&FgZtD*_u0*^ zvs+vjwmUEHbX?x$xTeQ`W1ro|e!DFbY`0Ic+db8O_cVw7GaV1ibUZT0`N&+S6Z2h8 zEO5Db<#FEq$h4Rdj z31pDc`A4D}2;AWwiIih+GzLw!?oOKV$uE4y%aPj5GSlOVhMl&RC$g_;Ch zMciCnot@p>Ttt)wn)n?ZMOQmGI5~VE6#VUxzxGla`(C`6E|O*yyg0| zZP%x7yFP2@trZ7vuRL^n)#2OAj@(;)+|#Zb?n!vF-Jh+<%$ z-%#Jw+|t@+sVONYu4&oUq${SNHbp^9w@p_-UEg?~zPiASW-AV5d*ziY?NvFfnlnmIVwir5}B6_#q|W-@SKwsx>)b}(S%+{)Y$d*YF_&go->kzbr}F>5mPJBh$I(VU8KE)@hYx{#B0>@#0S{kH NJlZYspM{CR8URhlcccIS literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/method_time.gif b/inspectIT/icons/eclipse/method_time.gif new file mode 100644 index 0000000000000000000000000000000000000000..f4bcf48b39132de74de87963805f8de37c356b7c GIT binary patch literal 629 zcmZ?wbhEHb6krfwIOfN|5IW5yzu%;MwQKDb@A}QY4O{#gxB4}0@onDW-@MhYc}HN| z?x40^!EHN(+jj?b>bRcQc z;grdTlcpR_1EC`sQ;z1%I-WQCMA^bK)l1IRE%_37KL&)Ruw#lhPv58YmM`1Z0R_f{XdyWzzBjVB&#I`v@B#b?KFy*hpO)%C|8 z?>zf(@5Sf)FF!qb^Y!W5uQ$H@zx(z7(;xre{QCd)*Z)ty|9|@P|I45M-~ayq{qO(Z zfB%7iVNe3ae}c|Msfi`2DGKG8B^e6tp1uJLia%KxxftphbU;o9MHB=3-iG?7=4M}O zCI%*J-PLO?r~b>KlBSoLt=)J?H8P zYZ#d9Yh&;@G}(q*)lv22$+nIzUo9m|Uk8<|4!$>T`pSx!->?_AzhNdO+swym=*wp7 zYs=jF{R`1VFtODC9w^3mJ~IPgW4=So6i*y^D)qYt4l~O#udL09vW)&j0`b literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/methpri_obj.gif b/inspectIT/icons/eclipse/methpri_obj.gif new file mode 100644 index 0000000000000000000000000000000000000000..298871653f056a8123af853bd587a3a394a208f0 GIT binary patch literal 183 zcmZ?wbhEHb6krfwIKsg2JR$LfrojmVlM|YTCuDU#`Nn+ojrm&Ea6(e+`Q+}C&Ne6A z?M~WReVf$xJR#w0P0icp=KufyGY}0Ff3h$#FbFc}fOLTDWMDN(Q0Ys_oR_g`UCwH~ wf;~&Tl5cyRGV#A}pn33MfPoClO2#9M3=T}1PDxg(O9XD{96I`To)d#L0F!by`Tzg` literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/methpro_obj.gif b/inspectIT/icons/eclipse/methpro_obj.gif new file mode 100644 index 0000000000000000000000000000000000000000..563743d393f23c877d037a37348c9f799005aa88 GIT binary patch literal 181 zcmZ?wbhEHb6krfwIKsg2|I>#5FDL$gHF5i7)qQg`cF$1XJyZSvvl;8!CMAi|&nQV6n>fz?Ps zwJ#-eUdF0*IbH&~j0_v1I4TmBoId!c=P<+44xhfs9!jheEmS+E9{!nODz$daHc=)9 FYXDFdK#l+a literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/methpub_obj.gif b/inspectIT/icons/eclipse/methpub_obj.gif new file mode 100644 index 0000000000000000000000000000000000000000..7d24707ee82f54aa9fb10d1d9050013cbf161a7a GIT binary patch literal 193 zcmV;y06zamNk%w1VGsZi0K@VRxXubL!4|)qjO}gg>klxZ?TGXw~#-V zU_Y2&N}FX?r*L1YbYiM-aj|xBv2}#Mgo3?-guaA=wSS1Yfrz+)iMWB7#*ml2h^x<; ztIwFU(w+bR{{R30A^8LW0015UEC2ui01yBW000F(peK%GX`X1Rt}L1aL$Vf5mpMgx vG+WO#2NYmJDM}^)l;8n@L?90V%CN9pFcyU&MPO(u48jTlL$uClRtNw)MiWcq literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/next_nav.gif b/inspectIT/icons/eclipse/next_nav.gif new file mode 100644 index 0000000000000000000000000000000000000000..072b1844572646fdb57124b6e938a465daac8d58 GIT binary patch literal 332 zcmZ?wbhEHb6krfwxXQrr@Ar$pzh3s)RRH2yZMG-dG{LyHRvkqv*C;prFW}Cb1QH0&9u|R~HDbFBMu_B(%Cf zXj8fH`cmQLIReYF1^)m4&p+|z+lLr1JVKV69Zf3ff)rJI#S8AmOS&g!objY zXp*F{?P9eo9}@`oK{Iw9&}i^SBU4D}c3FLN?5SiQ`V!5RSYo{04T literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/number.gif b/inspectIT/icons/eclipse/number.gif new file mode 100644 index 0000000000000000000000000000000000000000..44ed0ca8865af30aea25ccd5df12b973a4dcaa55 GIT binary patch literal 600 zcmZ?wbhEHb6krfwIOfCCy}kx-xz9)#;nB zP2X~T`j#8hx7?h*_15&Qx2JCdqC3;K-JQPe-t_JF_V3?+^6d3f=Wm`qfAj3c+h;G{ zId}2S*-LlMUAleY%KeL1?q9fi|KipAmv20}eB<$z8;`Esczpf#v+K8?U%&J0`km)D z?mWMJ|Ml$$uWmnheed!6`;Xt<2a-?T-GB1_(eqD_LFn_-SKpq!`u6now`Z@vy?Fcc z<=dYx-u-+DLcd;r`1AVXABG_d6o0ZXaxv61=zx3xiW3I*tqt|{qAjg$txYZB-95eH zEzP}R3{x0;Me9W*B_t)D$t9d9rdcGX&+86vf1p6cnb&?^>^_s;r`- zraDzkS#<`Vwz`(KrsmXBni|6OygIr!<}vDA*V7TI=g~LNzh!7-U}#|cfZN2>Wd0`; zGZVpjE^{jj#!pi$ElTcqZ%A(Em$T)O`|`1wou8v#NQQw)h2vP4wF3$yFy z*4M}9)Xyz!oKe&`y`*VsN%Pc-)`=CZlj=IU>pObdx?5X&T6_EJCr+%MGO2RfwBlJ) zi)T(NnLi_c$*k-pv$L1a$ymK0d;R(uyEb+0I)4B9r9J20{(k!Q$Fp}oo&(YQA1^@Y z=Zg+etB{(S!S=j-=BUw{1h z_T%sOAAf)R1fqXGe*OFZ|3Aaf1d9Izor_WvOHxx5$}>wc6x=<10~i#4vM_Qn)HCRS z`~-?92KF5d^-aw!txbxGDr(HC%xWqf6Ppy&r>Zkgn?}tz|6p=G*MHoNk&~# zOKXO@j>c+j*(NDPFX?4Gau#-xhBY zQ8zK`VK$TIkv29JZ4y#9|L{Rt*-2U2T)2r(UHLDM6;BIG%7n*_3)ucd8Qw5le~(R$ un?>`%@db z5y@ZD3V!xY|Dfyit$p%;hX3cxG%nZ~T?+AdykhaA6^qXqs+}`bKWC(V$=Uj0an8$* z#*5|#_x(Js2Kjt!sQK7b`>nU@{la;_42qhte=uO622lLT!pOj&!Jq>& z4CE&UHb)1ME~8EtHIYQuML!!%CUKp*Y}x6yypO$9(7dy*v!rF|NsAISC1a<)W3UR@vdsEgKc^na#jD`?)!JH;qo;1f7d&J z3K)=p;!hSv1_o{h9grBv3H(WMHrc0NN!jWB>pF literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/package_obj.gif b/inspectIT/icons/eclipse/package_obj.gif new file mode 100644 index 0000000000000000000000000000000000000000..131c28da405493661e3253ef79a68bd273039295 GIT binary patch literal 227 zcmZ?wbhEHb6krfwIKsg2^W*Nf7neOfxp04z;n8NJ+xzDotkS){bH@Hst%K#-*LO_c zo~yCDQ0v_4?v)A3lSAd#C95utQCbkGxF}NT_=2WF8}WGs5taT9|NsAIzy=h5vM@3* zNHFMtBtdpEuqG&|^`&Ia(}-MpBVo@mW@+b{B25<}cFdc?!Kkoc14n0vkh1`XOwU>7 z#al8o_@;D=?hdfkdC)D9Q@O@%Lfqp;ZBt~9C*29`GMF2XzQp8akWQVjDvMC75PzEx Mi%z;upCW@b03m@=3jhEB literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/preferences.gif b/inspectIT/icons/eclipse/preferences.gif new file mode 100644 index 0000000000000000000000000000000000000000..1b7ff1ff0185e14f1448003107b371b1758ae233 GIT binary patch literal 390 zcmZ?wbhEHb6krfwSgOwun%y6k-CtHXHK}}Va`~KwvWYE~{oOUag{`X#Th~l#=&GH# zvu@(f?rE#rW*yqSY4_F7|6l+7|K{iaw?OjO|98LszyJ0B!>|7zf$ZP^KmPvz>G%K7 zzyE&$l7Iex`Sbtlpa0+f{Qv&v{|_MJ@Bi=|L5QTzyALJ4P^ZL|LfoX-~ayq z`S%|P7)S?-{{)?jQWHy3QxwWGOEMJPJ$(Zh6o0ZXaxvI5=zz=vd6a>z??8OAhmKV1 zge65Qa}<=jGE%(eT1g8WJ^VGmLq+7&^rBXom=}kS9CQ$1np%|jTO&ee=I^~df{O9MQvadR>=C>NEm pYH4ezZ`VlGtYgyD)H!rWGgbHaiIc~}-Ojq$U$}Vbvb`gNH2~M{vQ_{9 literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/prev_nav.gif b/inspectIT/icons/eclipse/prev_nav.gif new file mode 100644 index 0000000000000000000000000000000000000000..07164754e5ca231666d9daf7fa4a9d12e378a52a GIT binary patch literal 323 zcmZ?wbhEHb6krfwxXQrr@9+D6zn=X4cJ<${r+>d*`SKk--`OT*-X9 literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/prj_obj.gif b/inspectIT/icons/eclipse/prj_obj.gif new file mode 100644 index 0000000000000000000000000000000000000000..a4ea580d278fb727e4ae692838877fa63c4becf9 GIT binary patch literal 351 zcmZ?wbhEHb6krfwxXQpVwXtJrV`pb|Z&Bgo_>{Q`Df1G5Wa`}H^qKLgbHn221;#86 zie2Oyy23SVg;&(l)`=%9{nuIstg#PSrQx<&&vS#m*G7G>4W@o;CvAN*Y1^AgTVGGw z_ImEoPjiobns@ZmyknnMUi-Q7>W`Jzer$aB_t(pL-|kQQ|MAfO*PGv5?Ee3B$^ToO z|A8VGOaEW3eSEO?=BC06Ybq|Tt-P?N@;?|b;0205Sr{1@Oc``Qsz82XV5>PWtH47? zs^4Q~P@BxTjDV;&5*!R(s==>VnJe}-&SEIintfiq!@Cwn{GNn@o~+6#1@$v`9K5*q=#}W4&e*)}*u1Xzf}VuJ z-sF-A$tC^i<&(0jr{>qs%&(tO+%&tSc}{uTyy~t+b-hb>9K5jS=#}H=@1$2u%B-4_ zT{Eq`eSR58c42ML;>HQfnkTL3n6`Syp^MuOUf6cv{N7_%4xPSn?EIblh8cBzOWLQe zX`Zyad)B%o8xAbnbZG648%IOZ9jD3@VVQE&fGY5;m$!2d3(Zw4gbM_VJHB_pDc_F3?2+RAjg8@ zgn@m2gGW%oBV`XLSEUD@0Y$a*stlXr#-DbxQ-6jPM7Yz+l4X3NuO%2M6)ADKiQeGH@#Jvas$*Wx6lO+AiQZ Q$Kv9IqumnhtV|5n0C~#6Jpcdz literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/pview.gif b/inspectIT/icons/eclipse/pview.gif new file mode 100644 index 0000000000000000000000000000000000000000..4aa54a69d9d7aea424c6f2442e97952d83ed7741 GIT binary patch literal 219 zcmZ?wbhEHb6krfwIKsfNV#9?S4}M4_%s8-uVaEc711mT-%wSm1!SLe2h7UIyKHTW| zaH8PFj*K5SPW*UqOw zY}uAA#Zi(wf1SXa8u^aR3`qqN3PGB(ixw1UaJi?rxO-Tv<7_ICvM|}#dpyxQ*5(}d j+7QnK2Z~a o3BR5Z!pqZs_zcTbD^B*=oTn#qbZ(8$S+w}2$%`$wgcupD0piOzbN~PV literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/record.gif b/inspectIT/icons/eclipse/record.gif new file mode 100644 index 0000000000000000000000000000000000000000..e35a79ae2ee396ff820b881eb8cd22bce08fcba0 GIT binary patch literal 269 zcmZ?wbhEHb6krfwXc1yK>2CMYKKPS&)a%fMbBR$OBa^=*sDkSF@r7C15=jQ=gdC92-CB+PiKUo;L7~~mr7=QrePzKh}2kLz(ne$$V8?rbU z2ud+}Gn;Y?O#l4&KYwiJMh}VpPS(XP77FtMjx+`^Nif~WIlz#>$MRr9f`h6c_lpw8 elgt|T3R*YpwXopZIXCla)yv?9#wK+|25SJehJ$+m literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/record_gray.gif b/inspectIT/icons/eclipse/record_gray.gif new file mode 100644 index 0000000000000000000000000000000000000000..f362b62938f2bf217d3f5e58ac997045d2063fda GIT binary patch literal 970 zcmV;*12z0dNk%w1VGsZi0Ou+I000010RaL60s{jB1Ox;H1qB8M1_uWR2nYxX2?+`c z3JVJh3=9kn4Gj(s4i66x5D*X%5fKs+5)%^>6ciK{6%`g178e&67#J8C85tTH8XFrM z92^`S9UUGX9v>ecARr(iAt53nA|oRsBqSsyB_$>%CMPE+C@3f?DJd!{Dl021EG#T7 zEiEoCE-x=HFfcGNF)=bSGBYzXG&D3dH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}? zK0iM{KtMo2K|w-7LPJACL_|bIMMXwNMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuy zP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7SXfwDSy@_IT3cINTwGjTU0q&YUSD5dU|?Wj zVPRroVq;@tWMpJzWo2e&W@l$-XlQ6@X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R;eSLm@et&;|fPjF3fq{a8f`fyD zgoK2Jg@uNOhKGlTh=_=ZiHVAeii?YjjEszpjg5|uj*pLzkdTm(k&%*;l9Q8@l$4Z} zm6ev3mY0{8n3$NEnVFiJnwy)OoSdAUot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5( zrl+T;sHmu^si~@}s;jH3tgNi9t*x%EuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#p zxVX5vxw*Q!y1To(yu7@dCU$jHda z$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD)(b3Y<($mw^)YR0~)z#M4*4Nk9*x1lt)=I7_<=;-L_>FMg~>g((4 z?Ck9A?d|UF?(gsK@bK{Q@$vHV^7Hfa^z`)g_4W4l_V@Sq`1ttw`T6?#`uqF){QUg= z{r&#_{{R2~A^tH#a%Ew3Wn>_CX>@2HM@dakAZBuJZ6HNsY-wmc&FXI1!Y`kDxRbyku#T$&-^TT`GtN(V~|y7a_t^Pz)e5ICGi-B!;COI6#BK su|ty}Nj*rD>QTv2Or@t#D~W7s36Up688;eS*zlp&gkZypJs1!GJ8F%_eEs4SzezMMCTJc$9^k8l4ZE^K(a`kz6^?HBifPUSCfmNj2?Sg=1sN402i1&$wdbHp7 zqMDJxDAlE;PvbI|Lytz?fU=k`v36y|MB|&^ZNhv{Qvd*|M&d=_Wl3&{r~^} z{{R30A^8LW3IIOlPBUa!H&pC3DrWCBF`c$vg&$Q z*rKU4Dw{_o6Vb(M+Gwm XGzJ4R1W*Dp)YSn{0NL8vP(c7YZB3_T literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/record_term.gif b/inspectIT/icons/eclipse/record_term.gif new file mode 100644 index 0000000000000000000000000000000000000000..7ad24f4c28d7c8177c53b4fa94e74211bba98974 GIT binary patch literal 353 zcmZ?wbhEHb6krfwSgOEqKtSP;n$8OagCiy;$DC|lSU8+?xBF-xeB9spM1;pD@2D36 z0k1<7&Lu`&NGSLinfxUo_jPXJ*Sw++6%}7AE5BCNKWnJF+tT>9so`sT$M??G8#88n zUo`8-iWM)f-v0mpKLd?{;!hSvE(Qe#9gsaBKQXY?IZP<<(2?r*nDG3Pfr*~nlH&;n z3k<}Zw^&@Q*e{j)F=^q9^CG90-steSSKzrM(8Pi7#~;QdjkJWa!4-LeYm0=|77MK@5?WI%v@BcT|Ns9CR0WDZSr{1@R2g(Yrh@#$z?O1g zQh|q#RHw+PiyoXB)1AE)3U1~skT{fJ?U6BUYICAZn$5HYp3GD7dKS#fQdLEHHy z6I^aRo~tUt;P2kVq1sNF`2?+`R|Nmz| z1&Tje7#SG27<53wATtBYuHJj2L#xNM)vQ#JRl<>hDKU#-N&yE`p}?0M(Gw4T zcQCOfDoF5h^e6?|s2Q}nBw9^uav2aEj#_#sYT2Qv zV)qmkv%1PvXL4rV0>rik?s+1aPxR_$LZ Hz+epkV|PIn literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/remove_exc.gif b/inspectIT/icons/eclipse/remove_exc.gif new file mode 100644 index 0000000000000000000000000000000000000000..559e462985f439553de36c89f65392eae3e9a44e GIT binary patch literal 159 zcmZ?wbhEHb6krfw*v!Ci=-|O)$BrF5cyQ02Jv(;n*t~i3x^?STu3Wif$&v*N7X1JJ zp8*vp{$ycfVBliV0SSZ5U|{hHIO(~1uSCNtU5#UEjBbj3ZUQY)yXJ8fa4;1Ld|~WA z@!)sI-4+*(HUmTFi3NKYgjzF96l>i0I}eMb=?0hv=!;iZy|UTOS9Ern3j>2S03Llh A4*&oF literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/server_instance.gif b/inspectIT/icons/eclipse/server_instance.gif new file mode 100644 index 0000000000000000000000000000000000000000..5d891e67ab603252c1b83d8d717df92b81a057a8 GIT binary patch literal 627 zcmZ?wbhEHb6krfwI2O*p(9ggyfuXh|wxKJrsVk|aC%LUZwS7XW&yu3f$(dbKGX0ho zbx$einNrw2HMeJKcHh*(-f6iLrxx~4%j=s~)IY6c;4kkWiY87k?VnLRX?od& znS~Q(mQI*iHW7$sl~0~iHhB(^teCNA*Q`amXDm86d-2Y>OLxy*x@Y0C0}EFkTDbD? zqE#oBu0FJK{i)TPPOaT~cGIrA+YUb1b^OJ?6R-B4dU){6>mz4h969^u$hqf-&p$nO z?!~e5&rh6td+OZB;}>2Yx$x=2<)bGry}W$&_^GRJFWmgR?dADxuP(fN`Q-iQ{~v(p z%l}Wm{(t`c|NHO%-~Rmn4n%+d|N8&`|34rE0fvDCivO&fi&7IyQd1PlGfOfQ+&z5* z6w-?Fa}`{Zb21BxQx%f)b93|a6f%>6L~2fIZfaghu|i3HeonC-gW^vXMlObW1|5(G zKrza|KB=L;sos-UQd&e>+OxU0o+r%1JuJ+Ir>|KwjDgYKQcZM5lY9sVw}GLG{GxgV z0U*r!~>j-j7>CN%n%QDGc|YA{@5pBr>d!?6D5$K>f+GI%+AB3aA?)T1r7|> E05iDww*UYD literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/showcat_co.gif b/inspectIT/icons/eclipse/showcat_co.gif new file mode 100644 index 0000000000000000000000000000000000000000..5ef0ed7f13c7b17f527bf3fd1bd49edf04844b04 GIT binary patch literal 218 zcmV<0044uNNk%w1VGsZi0K@ov_K2=INlov2DAUCeXWj``d;4+=ctxhW_>1 zr+yr*h9CRehX4QnA^8LW0018VEC2ui01yBW000G7;3tk`SvpdxN{;zSR4h}H3r%=F zJ?_FZj9@V0Di$x6q|$P6SvJt<#^UN^Fd9$R({Wum;4r6RERzg{w7Ot$6%O$Dq2L-I U6o`8QA#ec#fPn!7Wn54!E}icoQ{bso79d~jEnnoVP~@#t=BHW_s9qJM zRU4*R6|7Sirc)oL*ATAX5N^~QW!w^D(h_6T92vg4C}MSC1dv=+=({}4cX_Jsved-& ziHRE$n)fv}?`>$_+nBpGK6h)}HxkwZp6 z#@|enuI}!dc^e}PxRM+XU(Yd=!dWtVN2m6K!RaA#IlQc`AN@nlq2V`TDYnCm}( Jftw?PH2}meT_XSh literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/storage.gif b/inspectIT/icons/eclipse/storage.gif new file mode 100644 index 0000000000000000000000000000000000000000..446bc308a03db88269b11dd9e51057412f9e5a1f GIT binary patch literal 607 zcmZ?wbhEHb6krfwIOf4{A&LD=7TeQGAY#8;D|Ww5^jb5|r55GKO(K`ulpZ$=Uv5{v z+OBx5L-A%85Gmd4Qob`;@aO{3`!hu!%@KYyNA$s5@yBzeA1#u6v`pmb68UE<6dtTm zeX&~k#TwO@YgIpOl)t}S`Q?7g_xtSM9#wsNRO9_Irw=ErKb)}raKiETNuA%Pbw8Z9 z`Fz>r^HtNIS6zPI@cnk*_uB*iZx6!1J&5@BAoAOTsBaJAzCB3%`6B=4i-Mmo3x0k` z{P`jE_xtYO?|XiK==lAi>-PsBqyNv3Ie&i4`}1SrpC5}Dh6qsnXX#v&npl#WqEMb$ zlA+-4=^LPsR+OKs;F_G1Sx}s+ker{Jo1dqUnG7USb5e6t^Gb>p3X1Zx^cWO>vM_Qn z)HCRSJPV3Y2KM@f`ljZV*0y>jQ3)|YHl_A@MJ)q2dovvc#dc|J{i)Lx7~8o;^lS}P zB$-&++10J*&sfvOx!%prbOviXkGhq+qsgvz-hJ*4#)sSZG_2el7jU%mYg)NFna|*A r7f`cwakjX1M_AX!NL7-Vp5*;ZmuKvkZxdQ#8(QohUE!NheIbecOcq;C?ewRWY)>oM z@79XluM@r2%yX$lxpBgZ$4w%a+ms$R3tw(mzS^#MtwZrE|Gt>LgB$0)fcOkU#wAmxmI=MremKr%HQ9v{BpnL+oP&) zk7|54Vg380&hOK@AI{r+zHIXOs_D@XwD$|NsAI81z8#pQUqA zYGO%hib8p2Nrr;Er*D8lT2X$kf@^Y4WCRP|u(P@+K%k8Q6Op>YJKdTHETiWaMQf__W&VHT6u~ovaKrG~1Q*ji*l6 z;Aj_-HgYi4R^Z}k=hwBHKVwas(0X@A%Ne}wqPn&oE*87m#dJMAoXroniF>Mhx-1ZE zKgZ_jYCS`^S?Uh!c{iI|&CCMz0xak4&2$vF+3zrNGD&$FzJALs*&@L3OiR^g0>{I| NmW3=6I9Qn&tO4e^%hUh> literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/storage_download.gif b/inspectIT/icons/eclipse/storage_download.gif new file mode 100644 index 0000000000000000000000000000000000000000..c23bff055458f65ef98b4b95d5001fa4d17c863b GIT binary patch literal 622 zcmZ?wbhEHb6krfwIOfN|8#BweV6kz*5{L5T7n0b|WU)Q11S0mkwPN?{M6WgTTxwB% z+$3_jP3dv7@a1;ptL=){Iuvhq0g=+pF6BFu1@F%keKbe-(HzkSbHyLem438H^3gJp zr%U9Ytx$NdM)k#NQ$DBT#u>Nqu_QMIs z-zRmB9sBtEwC;!VHm6Q~`h3~s^HtNIS6zPI@cnk*_uB*iZx6!1J&5@BAoAOTsBaJA zzCB3%`6B=4i-Mmo3x0k`{P`jE_xtYO?|XiK==lAi>-PsBqyNv3Ie&i4`}1SrpC60< z|NqZ0D1qWXOXs50#FEq$h4Rdj3>6=WNqRcv@o?qP9$xK~_jbkCVmc!hst7q`8^LDnH%DGTL%ihP$a_2(9y*{oM zhh_+A+k3ms<5?)IWAEi|GnH?lh^C#VhwaVVVn&YU8ZzuG3)P-Hd%^DOYslQIs;HzO aD<%J#nQ5Wa&L8Xw3X2&T85lTN7_0#w@z|UI literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/storage_finalize.gif b/inspectIT/icons/eclipse/storage_finalize.gif new file mode 100644 index 0000000000000000000000000000000000000000..737ff2dea4dcbea1dcfbc7a2632f61425c38595f GIT binary patch literal 658 zcmZ?wbhEHb6krfwI2OxL5iNWniTz9#+wwY@)y?wj+T@>BvOTS2U)E`Hw^rElaW zA1#u6v`pmb68S@`il41ec(6wG#cJiF8)`pol)t}S`Q*0F8+&tK?zg;jV9LdVv)>+7 zeS1{n=8^UfC#-*;)cJi{_rrOcYo|BeIlt!fRnwan_B_2f_2*TWpErCzT<`mK-}mdS zj&BeAzdZ>5_8{WhgUD|WqP{(d`}QF5>4Wos9`^ryQSkF+!Jj9y{ydxa<=M7B&*uMm zw&3T7)Zg#Bf4}ef{h{OchpyirfQu0QEFmIYKlU6W=V#EyQgn}LRwLNu7YcFPG&)IszP#pZf<^_LS{0M zNX<#jP0cGQRwyXS&(dR1{K>+|#Zb?n19B}WLK)aMHPkmXx3sp^Ye>jQ3iD{R*Q@GV zMEN-ws;IUr7?@9;uENwVC~o3or7g?G+0LuyHh;#NwwU!%zVrmU>StQ-^QI76U`-z8GlGs25MCdlbc z6L(lBhe*gYod<`SUE7%%SwwdvF*7hodvFPuR4iyxV6yC+V^NsG&ZwtklfhxAai?Lj Ly1N4x3xhQP(ZC1% literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/storage_na.gif b/inspectIT/icons/eclipse/storage_na.gif new file mode 100644 index 0000000000000000000000000000000000000000..0d5e0aabe8cca3780a56b90ff4848cfd5568faa7 GIT binary patch literal 628 zcmZ?wbhEHb6krfwIOfD~!^!_b68qigv@=<3Pb-0l{cf$;{W{TW%{-S{lpmKjJZ=)X z+@|!nS@?3h@}0?o&pRd@T_E~sj_{*7q7UYZKb|Z7V#4%CizFW{6M4Es{@DtJ2WwPc ztX6)xR`tz-<)1dn-`}qMa=+!xB6Cg)@p z6sIaA=jZ0;=P6_+1Buj})ZEm(l46B|qWmm92F0H&j9d)$3_2k1gCdlHeM&=pQ*%pe zTfL&NxClRoVtc)Us+PTlfx5gxyOf&d)amlf?R-KSX4*;;ENtzZDkk%1tZCy}Z*Q(Y zgT39+(b&OCZ&wq;(PIpb4wkxy>lwJ@nB=%l*jp{&J|gER<|uc<&f0JW?-8cEVoWD& wZH%5gd-2NAR7Y8YmGRb{dndkpV-@UhoXo};$<8OKl_ODdMq-J5BLjmq09nz=m;e9( literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/storage_overlay.gif b/inspectIT/icons/eclipse/storage_overlay.gif new file mode 100644 index 0000000000000000000000000000000000000000..ef07ae55a3ea635954e6522cf2b03ab5c21c4338 GIT binary patch literal 556 zcmZ?wbhEHbWM|-JIOfD~vrOPYwZQ2%zWYt$51YlFwy-~G6TRA{`m|l-TCd8jUX>et z%C{!~k?O5Uig%`nJfA9fYmUUj#qv*=$UR=F^kjw7Hqa+z}K6B-#}!@ zm)rhd?)ZJV10;jLKlcCrDC+y;@Skr}f4!;w`62T6`>x;byMKS^{QaTl_vhw6-=_Zg zHvP}H8GpXb{QGVC-|sUShAdG0XX#v&npl#WqEMb$lA+-4=^LPsR+OKs;F_G1Sx}s+ zker{Jo1dqUnG7USb5e6t^Gb>p3X1Zx^cWO>vM_Qn)HCRSybp>{26mr@dRb8sezAHP zbw>wd6+u3E6C-5-26hcwb6rJdPHj62eI*udO?ykd#XMTe7qRi`*jO1Tad4?xo2p4M WN{E{oDzGxu%SlTL3)MF=SOWlbIHvyq literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/storage_readable.gif b/inspectIT/icons/eclipse/storage_readable.gif new file mode 100644 index 0000000000000000000000000000000000000000..1650fa478f12981d09f958c8106e503d865c5cc9 GIT binary patch literal 417 zcmZ?wbhEHb6krfwSZdB-zgsJIzfSa8GtZ?K<;P7Tm)n#c zHw#~GSH9Y=*gb9aoymen7l=NZBm8KN=!3c9kLOB1S|s^snaI;6^3PT%JXoXpX`}r8 z?aD9rTfRN2`u3>ChZEMnPwM`ulzN@Ao~wKXm;5&~^3m|35z#{`s-!|Ns9CxPjt7OXs50#FEq$h4Rdj z3J}5vK*cuPaFYwTjs-N50%H&wmXYY5yrFpHJ^f^OitD~o<1`kryl3o uv&?4Aw`c6_nbW`2jA^E^@eGDdv*gWK1vf6W-?~mhRBC#k{Tg>i25SHlS+T7E literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/storage_recording.gif b/inspectIT/icons/eclipse/storage_recording.gif new file mode 100644 index 0000000000000000000000000000000000000000..fc6e5901301ae4b856e0bf85369bdfff76a8a463 GIT binary patch literal 590 zcmZ?wbhEHb6krfwI2OS0$u;akSl}n`m&z$$qz1?0%i-wPv16Ey|CZL@u`}J#H4h+^+n-y6S6H)tBmq_jUF6 z+gk2S7QElya=*RxLu=#F1)`7U2tS%5`e3g3~bQPsCcH9nlM{(VyC_i5b^=WRY;HGO*O{L|Cte_nO@dBgYH zecx{n{J%X2|Mno_+k?n&52C(3i2L>+@#l+zpDzo3en|cOzWew4p5Grjet+ot{Q=16 z|MO$vpC60<|NqZ0$bjNc7Dg_HdIlYkyFhWmz`nYnzNxvTwXNPjSzSd|$e_Jm-^AM2 z!_iDnzg^4JYU*@7_I7b4b2l494L*T(VPn_%GuE_;uJ?6!m?7BiZ|vgdWxuQ0LWYe^ z#(%%3-Qju*UIjHZ1zvw&uLUAaauSmFB_-tiy`5%=H8FE@zTxC%_V;mq-o(hn@PmPg z(cjicgP)7z*8zq`M;0EtFTcMlv@DR}GRwHI!SUc^F`b|(5sn(|>`W{+Pa3|c3ouv% E0K>r0%>V!Z literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/storage_upload.gif b/inspectIT/icons/eclipse/storage_upload.gif new file mode 100644 index 0000000000000000000000000000000000000000..e0359f059b293af3bc8c015e5db8ce44ba915a3c GIT binary patch literal 616 zcmZ?wbhEHb6krfwIOfT~8#BweV6kz*5{L5T7n0b|WU)Q11S0mkwPN?{M6WgTTxwB% z+$3_jP3dv7@a1;ptL=){Iuvhq0g=+pF6BFu1&=NeeKbe-(HzkSbHyLem438H^3gJp zr%U9Ytx$NdM)k#N<(F$!KW&u1zg_v|e#^Hw4^APN^?)WvbKSny_v3(a&MEIj{c-6N~|;L`Nj2Y z4OL~>I2LkiSkIlhdIs-0H#^g*oC^gstlS++WEDXoircm7C){o`u3%R<2Iw zQ~4H(s9U-?Tijge@x;bRO@^JNmzjxy!Qhkcl72)fvB68a{KCNVXTFHL5R_uPA z=(T2^OD)Qen?x?RDLrl$zTB>SwO#RAhvJ>df(NGLA6+1Nct+u)Il_CdY!KX3ScyYKt$f&aG$;olxa ze0vc2?LpMH2XWsXB>ulQ>;HrGKVKI7{E+zbL+bPA-+sUE{{6n^_lJ((AG&^j=>PL$ z;h!Ii{{R2aFeriIPZmZlhI$4akn2Ek!oc3wP~X(t(%M$9Eh!@#o-7AfJFkJ${26Q7_}6>8+05W>7c%hm@wDF6F6^u5>tS`c z%~#Yx!FK^)yQrg^)-3G@$jq|&_=OQcSDl+FfK;}b8$3{x;VrTGU zXXu27-G77fkd)^rGUzWj>N-Q{LQ3*fUFoW>=Nv2OBQeJ+H_R?N%rrsEH9^{Kb;~zJ z&rx6>gigZV&&S}?J(grdnrT+1cyqIlSF3VZt8rMXa#*TyO{Zl|re$HUV1vGagS~;1 z#fQ;pTGDS}(r{t!!@bgCRL);j&QVIsS5L}UPt#*u^pcGAm5A)Z1VB_|{#CLN|7C@Cl%Bc?wcDjgu5wvLdtz`;U606VH6m`MNt literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/terminate_co.gif b/inspectIT/icons/eclipse/terminate_co.gif new file mode 100644 index 0000000000000000000000000000000000000000..dc47edf06955eca15fee8118b3be43f019cae3f4 GIT binary patch literal 215 zcmZ?wbhEHb6krfwIKsg2!ocE%g~JO6j~5;RA2JFK2q?TzFgT%MaUr4LM1aGI2#+5d zR-ACK*kNJuA|T*HMa7R5D{jn~@&EsS2I7F?PZmZ71|bF=kaCcn46GpwRQggf=VfHM zdA;5hI8&!2R^8~yn?1}ARoqe>boi8dCIuYG;hM1Y7_-A#COOZb=7bkjye)1o4Ymf! gw+AT-W$b32*d*n&{`ylDm&EhWl|vdDg%laA0bm40+yDRo literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/time.gif b/inspectIT/icons/eclipse/time.gif new file mode 100644 index 0000000000000000000000000000000000000000..2ac632e1968f3937e4450193a8621c1f77774834 GIT binary patch literal 686 zcmZ?wbhEHb6krfwI2Oeq5Y#FV+$t2@s-HE@B){J@f0k9jEW5J#j+F~tsusFeE%c~b z=vKWTw0%un*P5&uJMw4jD4DgrboP#lMF%PtAE;h-xOVxGrWFSpRvv9wb+mc)(dIQr z+SeTESaYOv&5_h`*`ceb3lz483rU1x7^xp;TmmAhvi zy+8Bl!}X`{@4t9|{mu6qZ@%Aq^ZEXp&$r+Iy8YqjlXstQfB1Fp)1UjF{@nZg``+h2 z_dox6^yTlPFaI8Y`TO|GzeivHKKcqI|G)bB>&>^{&wl)W{^S4K?|+{C{QvgHpZ7oi zz5f0G)t~>b|NQ^*>(9sEe?R^H`{noFufPAk`~Uyz-~aFb|Nr#=|M!3YzyAOK^WT34 zx&y_3md-_~i6yBi3gww484B*6z5xnpMftf3uE{x>1;wcf$@#gt`FRSN$v`4CCp9-U zucTO^peR2}k3sP#3nLdpJ%bL&*`NqzU_aMT-_)cb$i~DZsL|Y`C&aC2>gT7xBc#{M zCl?ekHzG1nmTyLrka&35N|u#j;W9!kTmhj{p`lD#p}|uAoZW)bA->)|zD$}&e0`+^ z>)DLlF1mRzsk^$lx*D<8Gr2h0+1NNRsX92=+Pg5E)75#eqpigBL0gx(UQk&7kHH^h zrXPD3T;Ms{sA!UtvB5E!jg!&1O()_DyA!LbWyzfzjf)pg;@`lsGb7=l#{>nb78c7- ji6M*GIgE~YOkz3QrrOO=3m%CIgbgx?IQMJ&mdO>LWnz*htSu=K& zOy6DxBxh|eoxP)S_U@W_dny(mXjrtbdfDNYr3af<9Bf#5w0ZT><~2v!*Bt3sbEI?4 zk=`{&y4D`;UVC)Y)+6)wUtV_X`s!1+*PXqy@%-IgXK!!0cz4^CyJsG~KlA9r^{4Oe zzj%NB&G-9nKHq-->-LAAPu_jL{o&XBPk-)z{`2U|-$!5mJ^u3V(bvC^z5>brufG0z z^X>PuAOD~K`2Y6%pJzY+zy0y&{m*}|fB%2==l||igrx_Q=WEmHu86nm!uN4~>5*o&2aU?8EQ@&o%-v6S1Ad{J&zn`By ze?610p{SvoCzGkCr@MzQ{7n*4NfHW$3P*)h17do92`@e{UrvT& dhJ=7K7Z=ZT7DfrLDK8{kdnRf5N^vk)0{|XSBR>ED literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/timeframe.gif b/inspectIT/icons/eclipse/timeframe.gif new file mode 100644 index 0000000000000000000000000000000000000000..1d862e9873a9a8a4577093467f45b69958ed70b3 GIT binary patch literal 353 zcmZ?wbhEHb6krfwSgOll;h5y;9^>R5trXtv9}w&x7_1%Es~0&zKXQUe>?D)e$#GGh zanYU@Nz*NprrV{>wojXV!reYQ#UUrf-X&|EOV<3-EK9HaMP3Dq0?Sso)olu0ayxa! z?W!&JCmwvV;>^qA55B(s`Ts2t{rdm@*Z&W{{(t=S|MRc^U;h06`se@mKmWh|{r~;% ze+F`a;y*#>qSVBa)D(sC%#sWRcTe8{2F0H&j9d)%3_2iFK^|pb%RDf@z(a>iSx{N9 zGDK{${R+m388%weeT=oPhrWnT=VAWeStxf`yDz7?v(uI>m40s)7JNA4djj0M-kA Aga7~l literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/transform.gif b/inspectIT/icons/eclipse/transform.gif new file mode 100644 index 0000000000000000000000000000000000000000..edcf398a168f6d465f27b122f07ddca9d87e9f8d GIT binary patch literal 230 zcmZ?wbhEHb6krfwXpv*EjOq`qS{hchB(!>ISoPBQ=G6(!tK(aMXl+{8ri`vl*}Yq` z`!;9yZK;^Cw_@hrmc>U~mK^O^cA|6HiH;Q~`q!SDu=ZU4y0a73otwG!$}AANI&0h2 z1-oxDfPvyaLFb~>#FEq$h4Rdj31pDc`A4Dt**ASED&FtGYPQ1460oR@Yz zkn=;P_!k5B06#seRqIYgZgNWuIQ7zHPrp}ir-c~1lV{SEm11W9<$X*!oHsx4P*fkrfp zkdUUJ*m5MATV9D~=q}xqQ*MR)7pRQ=FZjx%Qyl3#IqmYs`^&7v{xD|Uc&KP7F;pgq zp(k)4ai~m~(&B}xhMGsZ6+<9Uq60fF4U`vYJ~QEBW@4miai9VL<)PxS9w-z*60I^W z(k2E{NYzlThf;t^$(2LtMKaP@jif@uW2p?KWWi-Tid#Z*Ap<#uQ<*doI%k{~W&5X^^X5hv!nM~M!u#La@ zImXV}_N-<02`l`F&yytoF+Uoy`Grd>SC*ER^9w8~tUlTc-u8ln-PJ{_pIZ6_d0D))b^XTSFdZ${hFve zcv^Y!vQmFi5u0}(ZXI+tH@3IL{oD8M>^|>?V=veLG=FQyv9G_9(Tb>-T5K-=dHGsy T*s6DqlP~LgA8!8kORV`1ElJDZ literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/url.gif b/inspectIT/icons/eclipse/url.gif new file mode 100644 index 0000000000000000000000000000000000000000..4c82c1f66e19a180f121f4be5775aef6c69b22bf GIT binary patch literal 556 zcmZ?wbhEHb6krfwI2ORbU{lWEP|4s}#o$=Y=v>97kb z@Bg2E|NrsJ-={zPfBoX;lUIMve*Ayx)Bh*0|2%#D`|0by=RW^`_v+8{H-FE6{(s@~ z|M#!|T=??;<-7l{-~YSx{r}64Ki_`*|K{_r?;rnv{`&vpx4$31|NZjw-}j&Yzy1C7 z^Uwe9|9=1a|M&0zK?f9nvM_Qn)HCQX00AgY7}z@->YJKdTHD$?I=i~$q$R{BNXbl= zk(|&ZYoepAZD3(%ZEazot<@x^Yi?xatjgx5#%OP<-z2JQY3889>7~x%YHO(3B%dvbdA7xW(}nRKXp+M$YRM=PfvX_|AQ zZT_jY#b>)0pY2$Bu4~1a?iFXdSDu}^`t+prmnN;hGI_(LxjU{c-hFGwX&}0D^!knS zcW$4%_xRe~yLX=5dhqPUgBLF!ynOxS?W?EnUNH~?ivNt9i&7IyQd1PlGfOfQ+&z5* z6w-?Fa}`{Zb21BxQx%f)i&7Oblk@X{ti0sZf|6oA2F0H&j9d)%3_2j=K_S7w7I0vG zfrpM%{k)SKPZVY(U7vw8pD9sGYH>i^m7 z{}+n?Uupb*wdw!W=Kpu5|Gzi)|MMOH&*W4G3O9s`Hie5dhKhAYOSDHz^u$R`Ns&F; ztb4Lu|5%IOu~xma-9{(d4NrF&pY1liGsFJ=Y^MivoF6U?J>712zT5O-ui5SC4)jB<~Eh3Ck(|MR1tY+CYw%7@BZ0uHG65^MT;^$&kcave^ j77&vV;AWL^lhtKopEXNY&Ru%J!UdsAmM(L0WUvMR>~MP$ literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/wizban/add_wiz.png b/inspectIT/icons/eclipse/wizban/add_wiz.png new file mode 100644 index 0000000000000000000000000000000000000000..55c9b88dea11db6c5a0007ae886eccf646a559a8 GIT binary patch literal 2068 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 literal 0 HcmV?d00001 diff --git a/inspectIT/icons/eclipse/wizban/import_wiz.png b/inspectIT/icons/eclipse/wizban/import_wiz.png new file mode 100644 index 0000000000000000000000000000000000000000..732631c0ac7579a2823f323daa1d6e68345c9205 GIT binary patch literal 6650 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

t- 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 + +If you can't or don't want to provide attribution, please purchase a +royalty-free license. + + +I'm unavailable for custom icon design work. But your suggestions are always +welcome! + + +--- end of LICENSE --- + +-------------------------------------------------------------------------------- + diff --git a/inspectIT/icons/fugue/arrow-switch.png b/inspectIT/icons/fugue/arrow-switch.png new file mode 100644 index 0000000000000000000000000000000000000000..ab3dd3021d249143db2c59fdb8b84b72b1a4cbb7 GIT binary patch literal 877 zcmV-z1CsoSP)hadrQ+My~I{) zyrdVbwANN5QY&huDhgU?!Qz7tKJ`H?6#6Fw9~142f&~A$Obj4|1knr@T_OJA3C@7y)Zv~+L=evrb460nahyKfw>q|6 z!|-OkUF(83pL`j@m_iZ>uhVe+Ky7_mx6SW~T>Z?vwgY(&kq{6CcB;AFTeI5dE_W%S zO8`MkBq$u|SQ8w7IzNO75)yw4DnJ|)RG(W>_q0}eAH4W7>~V{My*vDCObW719WcQ` z4IH4AGoLS9Q9>cYHzuM|z+YBYTUF|d%x6|vIit06XP`{iY*CbeeU2r*MPfgb zdX}0!-Oa%p_ddQxK`)51d{<~Y`+@7Q5#M@fRi$gYwwyWE(o|ciDym{L%X#}LHoKI} zJ`xBS!3tgGr}AJ~RYQ`ZfRsrlE%b%aJDN^d<9>fdNt_8vCZ$0(wvdU(wJ?#SK@7H$ zcMj2jhlOY^2OK1wOe%=&S}Ju>DG?j=x;oD!RY5l>T}bI!uh07xg9b33SE>V1jS2Qs z!vKAG8AN$Y-zBf7WU#tvLtDDNJrH3oQbkNV@$!RiY}!062G&U~!LFiOHfR|+wy%G8 zV|X&mlTgN3R46@h_0^Hn{lVL51xze$wQZPm;?WO+Kh?trmKFtq|Y*VI^UuJyYuPnnV<5@DFk zU;wmOMnUWu8Nutcqa8=QM}@DGRQR>(E&=duYDDpNp`X9M96{C(oNSDZhUSnrj^aKR zgr=i7L4SJQnZ5nj49G3Lzis56FK`kw<81oYV|#ImH(>!XA2@`o2(`F#zqpcsbPgYc zTxyoR8RPSkK^Q9uNRcT015(7wVtg=eldLh@{M6xh3iD1m_Nh+e+=jbgaWY)5EsJf z|Nmqd{{7)%WMX7sVPRr;{q6(9f^~-(7{9;Q1!4>@tO4RDK->@I_Y25sGJp(V0tO^A zGb4kmg8>5%2OGn#D{r5}G$Fen8rcg1a@q|1G8!NQxY<~k8TdHa8Th%_893Qkz+ym* z;ezEbhJs}vgMtMxOn-j|2I9Yu@zo2rAsf!f$n=qckqN_qL?CVe$HMXT3<~yf&^Z5J z1q(1Z`1y$en*j@<35r1;sQEt|FU&g3bil!Pn*o{tSrE=*%m7m5AofFGI`{}o2YRtx zB&CBn80i3*TuDg>VL%LwH-;r}`u{&oQqn;yl#YSZz)V9*IzY{Ez(9I%cm*%6bZ~K5 z4TcL)G(!RJ$9rf0##etvNe3UF-unCD(N#qt^FBa;0RTQi)Zvn{aoGR>002ovPDHLk FV1i2Q?Uw)m literal 0 HcmV?d00001 diff --git a/inspectIT/icons/fugue/database-sql.png b/inspectIT/icons/fugue/database-sql.png new file mode 100644 index 0000000000000000000000000000000000000000..0792546d493b42e91e362e2624b988a1e588013a GIT binary patch literal 748 zcmVJo@;vgzx z;hcJ}Zf?$Pr|%s9FeRZ6e&_qXbME*5yGl#T1%yIJL3EW6#Q)JBLQDuu9GOf8RaI5N z$;ruyY&MHrE{9^Vgr%hn$z{0R1}w{h*XxDP=f&XQjUi3bq$gI~+L~TlbJ~Ow4qrPo zWJCRN43CV#tTy&P_&g?mIW@)0!GHX+d2es;?Ql2@0k*zb!ilDn zXusS6A$N6mgXsn`&Z^Y&`Ml(UI7$+g#Jqt9JQ#a~@h9WB(svUZ8ygUJ2f3ixuOBc! zKmUfrnOz5Jpy3oIo<7Fh_umlm>vx|p`}wWpLKhB%F646;J1{hSzmx3F?mAG;Umu=U zHP&{pX)hv!=zkd9)9ZsZxZB@_KwTY1?%WbncqU6A<93skVXh|zqS5F?DwV>wxu19* z`-r~Q)38hxMztF?wf;kTz-?Fs8=DoE$Lc%?))-h^v8jPMaHT8_%RAWTz#<1b^d%CB zylQT4?xE4i+LxD?X`za=NN%inJV^U|h#hDm6CVpAer9xZw5M_tQJ%=P)JB$!kevxUfNS6A)L zwo4-xR1ZGBH{*NXz8SyKl}aU`q0{LmkydGLu-l=trw7`*yXn3HEogl{pNDufs(`>( z9BWBiYpc?%h7X%;Ox-CW(c$qZ3sztdu|ktrI1K6ATkwpI(q;43++5&AlHlz89M*Y$ z8pog`-OwL7M&O9aA(Ki$EEIyAC{`5<1&~A$ve_&gE-nUglGLBihWKwcQ`=EWS}6+>89S^c~%^!NLLY3 zAuxfMrO9(L30+>VvWWs4co%FxJm3X?C$Om+3S8K1Lo=ysA5xhQTAa>AsaPx*z``k{ z8Jb^5BJhY`>GS*lU_<$7{LAVgx8sphrKluPraR&=3k83RBqd^}G{MH9$L0{e7 z#fAANA6)VL=iO<~nd7*!W;JY$Zubx|nXIh1{QCO%ba{D#b;%C_1^}V}@;v7m+V%hd N002ovPDHLkV1k)<{I382 literal 0 HcmV?d00001 diff --git a/inspectIT/icons/fugue/memory.png b/inspectIT/icons/fugue/memory.png new file mode 100644 index 0000000000000000000000000000000000000000..4c71a247d68aaba26b00a4adf655d02a1e90201c GIT binary patch literal 349 zcmV-j0iyniP)%eKPc3{L}TPkcHt7gF#+sp2MF%e~8w}!otEZY4s#VFdJw9)2C0L7@kge3YPf( z{W}9gC<6oQ23DMWg;E6ukUnr2{Qvcz>GP-03_pJSU|?rwXZZH*8^f<(zwq)w>OuM- zF8B#_!RJp5uV23gV-Vo_!Nu_H^*5Y+kb00lm;ubve;FA*rwTBrvU4-s{`nO`r?TPX zgVclcL89>o15>qzDcJNnZ407kkb00lunYd*`G0BdiHp*=_uYOrm;a&^u{1~@I30kz v2*kWV%uGTW_zT3Jf%)hg0)+jEP)x{Z#$ z4n!i6UA$TV6omfVB6O(i`0^<#C2zQBX;UvTE@OI$uX z05K@=^k%9dGS513>V^V=3PiD3RBeXCVY~_bhD=C2{`svTGS513#FJOQM#~jr-MiGF zc;8Q!78P*4$U1Svlc#P7Js3wD`kVe2m+{WcxF@Ny(R1%{Bh&=l@qEd3-Ic!r3;=a7 VH9Kxj761SM002ovPDHLkV1nr!94!C< literal 0 HcmV?d00001 diff --git a/inspectIT/icons/fugue/resource-monitor.png b/inspectIT/icons/fugue/resource-monitor.png new file mode 100644 index 0000000000000000000000000000000000000000..34424f62fd47bc47c26b7c3b85b39018268322df GIT binary patch literal 1478 zcmaJ>e^3-<7+%WEkpamFku=wpn3KHQ-CMXHD+k`~-pUyVcn5--qs!d_3wOKA?p}8q zT4<3vNlgOF6+@?184k5-1ovb|Jx^W}1o78$_m<;xh* z;ioGY7ejlu*Yq=40PrZw7M04S&OF@1`*pO6(S`hi!Ulk>oRC0!su>w9V=7qA0{wpK z3@%zsR$r+GF-YIoV)V2v-FBj7)vXGGL|-(RvUA0vb>qie(s$##NvmK@k|y!>C@18gWdIBM3Nk zK}s~yTaLR(YATkpvOtxxEZ{I43N&v9%UEHSIi3a@IWeXDhJ$2i`9&>ixlmXn8^=LCrXQ-K7a1=l@_ul#WH>u3 zdLW>NCQwRmH(`ovD@mcK)oL>lnV1d{OC1S>XRwLfO{%k(Ry3DxOL_bZE;^TTjTxkcr4xW}Jv1Av%u2Wc$| zU21k)3NEin-ZdIMK76>gE3WE}1|tsUFWolM_f^xqLEi!W9|iNUCvy(pjQrTqc(VAW ztBxdw>fic2_3j%3H^NxW{-)g>6VJ3o2flpiwC9`h@r20S)`{B(QXPg6XE_N%L3scUc79>eg_FWUa)!1vH81t7K{!JP#@&vb-B{}KTaIX zh0mPUKGugMR{iot<8Bv z-V$9GwALgZyx4o?z5b}w4Rw4{_IG3`zHsq#fBt%e7U2l6p?S$6-Ohn}_0Q_C=ab#G HZM**ivg#mC literal 0 HcmV?d00001 diff --git a/inspectIT/icons/fugue/system-monitor.png b/inspectIT/icons/fugue/system-monitor.png new file mode 100644 index 0000000000000000000000000000000000000000..a139103e11d4302780e2813762fec1c680acddf0 GIT binary patch literal 547 zcmV+;0^I$HP)2X8(4{zg@xK#*eF*kncxk2$yD&UO#{~lINl?G>;oi~s{p~}9cuT_s*>8p4IFol#A|4t zT*7g3NY8Kal-%H1073`_V66hMs8@3D5G`v2)>|8&r&kzykD<}tS(LRPXsSd%0FL9J z(P(7Pgl^>n`lF9f)0DUe=s_$?{|5NJZ)p@%tyTwmiL7C*pv+|&O#kI0(AB?iO(t26 lKh}Wyn9LXd{mx$k3;>8- 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 literal 0 HcmV?d00001 diff --git a/inspectIT/icons/selfmade/16x16_32bit.bmp b/inspectIT/icons/selfmade/16x16_32bit.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ee599f80b5b1c1c4ed97f78641ce7274f84242ba GIT binary patch literal 1080 zcma))YfMvT7=Rx{Otex8RSL8PT1yLx6{tF@B3_4!f)PPCvOgQUD9(E#UZO^#iH6`0 zw7NlWQcy&Qnkdo0A9XWPyrj-8Vo>H(;$<S$mLESGs$#cFp@Ap3MdA~1bPGRnF z)Mu)^(*@li5u{24!(=~B^A0s3ukfvZ$eX#IL}M8?`*)v(QmZ(Mn>lGE8tr06#}o@o zK0z_s*iwc?6)|FDArrGJFeH_Uv-AVHkK&=c3L2?MKjUS z8&nTA5;uOG{6anbp9x8&BA@`GU;0gM{RI^ohSoyoCb?uhQCjSK@tG905|({8$}+ z#-;m7&pg6cMW;y12;g=f<-ccd*tzo}7F!*uRxhE3Fc1HIiOF;bt=@y#>ceF5$?7F# zoF9u_yqV2Hi}bG{M`r8nxX<;j$8w*2oITTnIr*^sJNq$O{ERi$3H8J)n7kOwUODqI zQqqE$ZAT!gR{B)STp^h;DDhsRG>0%+gW`BqNAZS8F#AZh1)1VLfo^O)Nhtx@^D{E$ zS2Vgh=^r4$?8jj83Cj8bB_A8_Roy$?FPJs&wA@J}&Ya^oU4wia59J0|rB-ni7v2I2 C85e#4 literal 0 HcmV?d00001 diff --git a/inspectIT/icons/selfmade/32x32.png b/inspectIT/icons/selfmade/32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..942d1b6211e5882540cb298046e5b00c399e44a2 GIT binary patch literal 1371 zcmV-h1*H0kP)Ah{ClvW}g zbKqbwFtSNl7|jU#q0x*bI*YKe#Oa2S#l+}D78Cc&MBVs-vd!ocQE`cXW^|%688b24 z914DnNpK=0${bay{SPAfhck{^5sX`T|e+-{id@$$|<$FK1H(1On6^%(VfjYPL(n#IW?C*p{Gkg-~m z6>FMwL5+*U=A-YxmG8c3S|Q@%!B-=d%tfn(=_1cC{f{wS0OL@&s_OO=Y)LDz<;15@ z?RHF!jG*(QyQdh+~Q(6ZW>Y!q<8c-m_oB{n{a{DSiz8Yk#Bt*xU0g zbClgtRm+iPk&97?V5%a||N7<}F4a`a z+?l&}!{T&;nM^aF&l7V*4pkq+AU+8TK_q3032S%klnL989>&y-QQa=lpG>z)Uq{iZ zdR5(Dd=B4FKT?C*k-E(@v%3kJ@jMK>bx=YR*F}nAgcy@GmXyT6k+D|J|EIZ`Zd!CG z0<=UXRRSgwOMp1$3c9~MrT0c&zB~_K4?nLvr^+0ha&gsw#R?N~3T`D348gYYE}6(r zwg+kCHXF2H&_HNLMD4P(8HMiW(YZY+(gUL~z60^{RQ zuC@IPOSdD^wi-RC(5n~<5LqBVoB`eXV9*c|k_~HiI58=fO5CM6JT{gHxO`R+>!Ztw_q<5V`JaZ!M+pf`E!sL~j@e)b3Cwe!UoC2M-`U3r|hy$pAdsD;Eh&HI+-U8t+0W4-ZNg~LAQmw$Re z0rE-tDq8Pkazv1NX-V42rS`_t7$AGYv5}tJTD2bu3lP#z^J2)Em+#5c1w8JETeM#v zn@C7O*-m6pAI%qC)ITxt@i_L>9`unIK1mlB3+JiX4wV$6y0jR-5z$=XBhnOdb#Q1{ z&S9mC=)ml_n~1hl>_wr`w0*|1^?7@wP`MA>&Yyi(sb}h5cpg>mJT#FzcT$fCXO5Kb zncfLYZlePW+6l4Dp=ir2fE)iPE>|>ga-fnhs2M+_mk@%pl7BWddPRH*(v^lKWEZaI d3hw^|7y$K)4PCWwg(v_3002ovPDHLkV1hYukj(%9 literal 0 HcmV?d00001 diff --git a/inspectIT/icons/selfmade/32x32_32bit.bmp b/inspectIT/icons/selfmade/32x32_32bit.bmp new file mode 100644 index 0000000000000000000000000000000000000000..f54a7ce4c38a1a42346e446cff221b854d6f5168 GIT binary patch literal 4152 zcmcJSc~I5Y7016IvdHoPk$vCZ@9_`;EfB{AgAX@AP#_vFeO=oDL_KOe$-bl_^<*DcA*jk7JZ`8k|(~o{4$d zUIs@|vugfNa(JH0aEJy-dx6 zi+e7d-4^nk>!|T?ADzx|3P##zaBYEPt2N8X&3>tJcFpDfE+A_L6$@9lDR_Hz5!&0n zL}_t7=MrJ(kO3v?E#~mD`19eyTAft6j6YR!eSOlfX=5qA{r0B3Ylq95QBu+flj)?) zAzTss|Gxj0o@K+%aXxJ9GP!42aB$*2v6oWMd&SQ#Zn?C}Va#O7Rg^4j+ggF!w||ga zanA<^9^(4-e__Uq_t4({2gJp%guj0t`Ky?-$lD?pw?*t(9^=fRCQ~UGX`7A_HnaIG z>Y55Gn=CTlpccb^l&CkEnuNI6CD2!Wh`zr2L)`oK`}wZ9$GJW^S6_byJ9gG1Y2rHa z`L5Iaj_fz3T-=wl&LxbM3O~O$ux5=JCr^HfOBcVx`SX`iZ90v-yh7O6WY9*OrMc2} z^5pkmHn+3JCqrDp>+S88Y%xB0G9cG_dTy}?AHmUa5rV_lK+$Z1vuZU|ZodUayeVV; zer*lypAN-3b*clOfBp^bKX@$XIy<|Onz{ix-FmdO{bk5*aPTpCeM9w%9G-JU58B%P zhy@Gw!^!EFu(ewbjdlyqU_MmtYgzZZWapx;_MD7&>sCKjt|&xO(ppqhoIznxE3$L; zMZEW2137VU}!sW~VKvB_02#?t*@|{Q*WW=*%nli^v$F?wcE91=3aCaG zz}|5ipO;Zf5gxvteYk?MveSHChws0?C(pH|UQ2I!dhX)T!83@~3a;qqN`Aj=W~`k& zuUp9dkCdn)L@K4USQJsJiQO#;>B(>G+x5Zn?Jx@ z(*axiUF6kZ^3)nkOR0q}wGOlClR^Ki`OTP@(}Fqa$MDL;YD7dHhP%gp1O*>Nimo0x zxgW@VJbUgMV&aU@&1k^neEQ zjJX$}+&#mh_i z=&@n_8Oas3zqP!b>|O8=F~B!aFLM-m`UVNmkPd2M=-Kd^i5~*Pfxd?tk_nmuDE*2~WRD#x}sqzY5;ZB%Zze z_3&m*Vov1b6Cg%Qp2A)zVPYdDywV6?KKlk45ExbsSIh2&;VQXIuN7r)xel6pBxJb^~s}92_xSFvHJa>BOCw%`Gi9BKxjzKf7PR9AE zuNUj`W^7N6?%sMhkFJ2rm`Zr~az2pxaZT9#|KdK-UlUV@xP(S{kl`cZz96P|fRTNC z@%oGWUNkRQ9zJ@>K2mv+_o{;Rnkc~-UddX>5=_BG~YxD literal 0 HcmV?d00001 diff --git a/inspectIT/icons/selfmade/48x48.png b/inspectIT/icons/selfmade/48x48.png new file mode 100644 index 0000000000000000000000000000000000000000..884268ae730be92dc9e23fe51f1e54d40a11a7f5 GIT binary patch literal 2133 zcmV-b2&(sqP)QzQ4TXoV&d@!!YnjL0ZaOZ0<#!3`0}OQS;|j?=RF3 zm;#1U~6UA6hL&!Jg#CsTIF8b8M+BO7@2H~ z42WW}9mWoS>O>DIn{j>^;6C9G6=EKv+hQva4;%rUIo=MAVUwJ3Dx_UxhFCl*c%g1QTXXkNZ^{hcqyEG!;NOrnk`>K*|LXa zSU9eydgiwyh@A7}vq&n@kSLx#3tIlH8FW%g0y<=v`zqW6f)E4FY#*kw4NRy{#JU%J z`KsrkWXU4vY-US6 zQS&ofz0>$C+k+L=XXDcJ$0n5nvL9Vx!2ka7NAUCdcW_SXXc+?Ba<7@iJD5kR?)b%~F>C%n&Xbx&UR1<)O+em{)J%rpe{eN$@kLa2qxF<;<2dEW`Swb1Jq2{YCX1K9cV}5jU<=x%S5hq-ROLRXhrEP(a9s$?tH z@k9tDCxbse0etcCV8z8r0w!!i!UKYYB1W2;I~7_F9032Yl0CGkI%?};MlRSche_GB-eOr%(+vbwz3jx@ff1P%MfAE)!r_&`_LiiXl_Pr zf_Bfk{eB(@d2Xt|Errw4uNT)D*If_WURc?qa|pfEg%h{-Gx>RsEEAx1yuKQ)dTX5t z%VZ>V{))%Z69?h@S6+r)>)uA|HvQ`pD<==7nSvIazxi`>|04qW@V=hkyd)#wAl7wiPsG@sbe#;l?Q2#G0(0Jd4`Py% zA_|3e{4n(0n%Bg7^_Y8uK7KzKMt~#-!{>60S97St+;|^J=!tJefF~hvEqjWKp$r!v zh2*C#UJ^0*aQOjAe2rl^7$WTcUI>uP#(2(DmNiFfoVeoCVrXk?+>m61X*(IH)gsVzH!7 znm!#SV3j&9EL46jErp$!2Q~e;n#RmPB4=Rbi9Ny`DMQ0dK#wlZ$#MzY6ihg#-&T5#lj3e<9rWS*7-~af0~EdRJS@%5f_0BAgW3p6=C8`b z-uZZ>8^_Vo79_HH^=q&s=WJNBXyJfFN-%sfG5jEG9;Ya7xOxZ^7}RkZqFkb$fbIMbJWw9R_{)hveiReP7oWKEf(g;~Qv;HX zMGeh)I*I3j`~gMOfLEA%{?}PvQo>wki2vLM0p}MrHW%s#{|GPuM9hmL4zJb!00000 LNkvXXu0mjf@+$AR literal 0 HcmV?d00001 diff --git a/inspectIT/icons/selfmade/48x48_32bit.bmp b/inspectIT/icons/selfmade/48x48_32bit.bmp new file mode 100644 index 0000000000000000000000000000000000000000..dacef8a2c5d9b493bc0fc0682270666bf9469ea0 GIT binary patch literal 9272 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 W-GGpYjgM5qAS}WpY{FQT9rzDLSc`lB literal 0 HcmV?d00001 diff --git a/inspectIT/icons/selfmade/64x64.png b/inspectIT/icons/selfmade/64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..4b6f30bac6be5a22cfe3081db436badee254706a GIT binary patch literal 3085 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 bzW@UO5P^)qE8e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00QDkL_t(I%XO1aXjE4e#=raS zb@FDOnMo!_6Gdz=CW{gY(kiGR)s+OR%R)C@6gQ=d&{Y-P8M^GMt3p=|;zlhMaV4nH zsHJKmnnoQHo2dEwCNuB7``-O`x|k*nn0|2JEY5fM!TAou2I@QYi^v7#K*m)eWsT(0 zEBXG9ALT}~Y_%aGFvf85+`~ge{y-eN?kI|aB#ulbquDH38kxRv`Zoae^$YlV^A|%; z&a{?;!F<4bITvMPMYA@xWGwAu+&@t4KJl3|>QNR>h93}oes=8ptsVYXErE-AItxL2 zOFpR8fjE}1TCwYQ|6X}s35}KWf4#r|!=S27&8`s~JUwzGC>Fmf6`=}@=-A%2tw;OI z+jn&|`Mo=XLVr(b^Z3hZGVwZ0c&@NR%9G_TGVYq}^Ehf&voO7warEdVhuK*^zV&|tpnq|AAD8llx!(7M^8-RO^?b_n! z`?~yKUvDYk9x);U$N`LdE#0T~1K>idWE0QQ=7f0w5mxFsM0G+=Fz=g_+q!^l0D!|^ z&yB~4Gynkv@|sHR0Jtz&L|Q8i#$c_1aUK%cjz}8&<@pB_uVzA}wSv(GAOaBq09;nH z0N|z7N@7hmW3*;jCK1OGv{C>OWGbPuhMC9#aA*)%7R}zS0I=S|eR8bt&!k0*;yr;S{E=4~oi4gj^vwHKw`1INL5ksP75S%^5b_U6O4 zb(t#Hz$SnQHmjTY`xkyDs~(WkhX0!*t!i&Z^KBpR7dgLu>_wRTeru|#wWa@}=5dpS zvO0O`au!Zr1rV+U2>^8buBz&9M!|EG^PBhhh0P`AF~-1zGYN~|-Fq57A0C$Te_jDF z*Fahq<#qA_bmmH3dz*?M?a=9xNf*Xm1E?^@-x685;Wz%zr>+vNp5O!h0|IHNi_|6G Q-2eap07*qoM6N<$g1aY-$p8QV literal 0 HcmV?d00001 diff --git a/inspectIT/icons/selfmade/inspectIT.icns b/inspectIT/icons/selfmade/inspectIT.icns new file mode 100644 index 0000000000000000000000000000000000000000..de1cd07e380e1164faf217abf5fb60c85a7c2ce1 GIT binary patch literal 37442 zcmeIb2Ut|s_CCDN3{|Fq!cb<0D!mATBB(SK!A7&B+*D0s`ZdPfm>BmtQz)Whp$Jpx zSW`6RdNrDxswU>gt)yZGKmpCDv??&2rLrVt`oP7KvHNv+k&+sdu(7E4`SeXZGIGP^bG{rro!8aL^i z9yhl%ef-@kJ3s$hQ)5$;*=(^k?EkL5p`qrZ_cvNC7He}`!;$^gw&teY-)?HQwX}D3 zEdOZN23u3h7e|{LEY|v(RgdoY^zo;S+dsSf!%qE^&r-VV@k1Z9fAGbr>;1hy>|F8q zvila^({k{guHQF&eQn_S@zuA}xw699MKg+Ll$k#4{kds6EtStLE1O$3x3q9JZCf;} z)Kubz_R*)M#`xn9~HAH11ltOzst=4Wb$vU#sB_SuYU5lFL6^Sy*;*e-Lq})chr&k z`SK7?ZCSB{q85J8?{IuG+f|`d5Q!UcC$f2a`+izV6bhw}x3`bVN6ER*q0t^j zr3cQaq|y;8d49&~ z(#XNSyBlR5I3p*- zUFKzyxp~PH3c1&aYS|O%k32*O=Rq3!uMIfFpMh%wn@D4iWALlPF)(1)JDUbS(QS~#;=u<%xi#bRD#5zL+zbA4@HU2VPDvc^m-)a+%cYkcX@r=K2pX;YnfRTIQ{ zi&x!?KRO(}y^eunJ2vu7q>xG z;SV1lJaEl^{@vOYEA;{|2>QU4&#m~OxBdy9E?X)bKGJgOQqSeSb8oL-_J@^Jcuw>5 z@+F(DpV{#66FWaR`j+|h)eEfsde8gom;CE@9zICtd?)p>T)t>%K z7a?K4e&wUO#}?Bi^dZf|3)g((aGX8U@A$f^{D6JH4v33A*Lpu|eE7kK_pp23j%MurKG61q^RVUTW`Istmu*LKMh>H z&~x$H_7}?TFqG-%@nwp+^NI@YYCh)Zz4}?l?t)v_mdQ)Y=FXWmf8#g(uN`@O*1Qd+ z9&?NHOR9E0T3A+F;#NAhxM2FsvP~tfrDdhXB_*#HTZ$}mcn#5Rz+aoj9HiHfpmq4; zu`GINf;75d9sc1!2*M(2(0B(0X|)Z&}!8_3r=1sQiq4o(3N4~5fP)KqGRLJ#wLGu^|rvI zxR~geSfAVa-Vb3dk&#i+F>&#UW5#ABEOvB+q{PL>L`Ox&skb;51V)LivGECGQc}my zN^2gtCnhB+Asz^^+PenU2E;~-x=G2Y<0nnY%6PqRUgpH~wA8W5W5cWK&+ZCO88aqj z;n>vi8QIecr%o8aeKP!!^Yz8d(YV~ zUA*Gx>748$@g4~YrP7Cc&@r$vf7O~ttNX8Abo89Rbm@ZK(fjf&FNp}C@a76Xa5%cV z9gahJlegL(_VYcLF7!CA@4wv}*C_n@NaS;>ckEa-Q!aJk#x)PsL@Sl&-}_gROeQu@e<;*PphiML{d(i30h9C3py8xzRch;9R{cp`l&(6=8kQA<#dw6=ueYshVI->OOkSV;#^X5j!mD9D! z5*OJBj}bB|*UA(!*wU$7$&Pt}bjoKh)=z+{<{E9n0rn@FGPJiKYn z(V8PN-~`#>IsMAVD7{%zb5Lw zTa_}ojO{gu*Q{0BwpSI7ARHOHX(&+#-g}Z8NoCl-C}jfSI5|}+Tu5A%GffA|*SL-p zWPwm;dldz6kb|y7yPT!z0p3P@*J9f!SGW?N#UDAE-XG_xTqpB_xF_+tePardduoC~|~ueUk_j_Zzh{&h3kRbgZDPh7jf+P`o94R%>K_%eL>|HBCU z+w^1U`ELcp?EaS$O!+^|kGC+tuCtx`Km0)Vy5n3ZDfsDMO~8J~Pt(Z=qWSYbpWprc zZ)%A9;s@BSUirDyW^1!`G)wVmd)~Uu##znVtS)A3j4d5jZ0M~!EYF*zZ1=3Ua8@g2 zA7%@0RyCQM%$6qX{2LnTYwMdV=5%eE9fAR5~+c(uVRhy+u zg*LgN$!xKLD|lP#E$^JXat(g=_w{`HO5G;2sWGYv{JjyP;y;TSn|S=zJ%6INug`w= z_{sBE`ueVXYH6^TnmmBVe$7qPtg~3ycHY*o`_h%(GlzG!*_u0dAGvh7@5h#UGXTq* ze3>LHqZH6$Yi`_m$$sTXXKe#*8nLOae%Hw>y~iw#W*$)BHch-`q#0(x7g}3dt?j3- zT=}4((PrZog;%dyLSAJrlV#3>EpL9O97R2AA<+s%isUj2NNtpyPTxv*H88`@7_ zzVu=}qGkaX;?Qxc1U!Sb$Xh$wo41^~db-on4Btg;u$A4Hdq1n^&91Nx72fI%U^oGC zVSVH3)dQe4&D83GZZJ5{qkU)EH!(YKg13$WaBFKDWV@O_?Y;PdwY3@0)GAm!%xmB7 zyZkEL5a58yW@}-DrM;tb+qRBl*G_cuEnG7MV=Pv)wQ=*_*Dm$FT?bHRhVh=3W(c=; zZr#?@(s|{7epp*mZ$^cttE;o4 zy{*lB^v751eQ(r(khMY-r`v6tHdI$uR8&=!SJ3yaU)Z$LXf#z;ud8h^qr$Vj@8CPv z?BBd(wYGP*TALcyucP#+6_6G#u@{c-4NS%S27( zdMZft2BX0+g6CIMob3Cl@>xBlJk^hUcG+G>XX`capi6!BPuf;I#e=Nz9Hjyuxnjk0 zOJBRzdwAKi{E8LNJoV%gro)aunSQ={(Jm6N-mkl!Lpf=sX@z?E@@JlU?uXv1e}3YL zXP+_EcO2^N?WEuLT@>u{o{N|It{>gD^06nMedg(>h2?aa?^BPh?YVmOuN99jdd+e5 z_-U$S`Px!%4rf39EGRI}RhVQ=qM zd;iso7cU8RuS-1_d;3qn#xH(o$rJSV!XxU(77AZp>+iqv(>KS?UUOW(vYUV9^g!>$ z9(VzVFJ9?C`?_h-{f|EKi2eb$2k&{H`73)r?@(MjccAjFyYF1O?HKk!40r@&?T%A# zKD%)71BSZ;@4ajOJ^I!^?t6V#{gb!da?iqrznk|!)7SmiFI~8#x4U2L`{3O}^Y6La zc$@6@J8ms2DlRQ6E}nP$9rQM@-`%mG==PeA`yAk*yxslMwJ+Cv0cu%3m&~K{UFOYO zFn_*buJ640a|=uL?_GgZ&n5c>$2;ZUfmBwNNlVMhN-N8J=A-EL^!`hZD;F+a9(Zcc zXN7aino7OP=9SHu@#xEE99J(ltab2(b4$%48J#;b|NgF@fBI9OrI402my9UGi94DP zK0Cd%w7tZA?!4Ir`2}-JX-R9TTM5jR%`M?ecNBkHvZH86F<;a%hcDu?is_uTB07gF z&XR&>YC5!>CPdJ51vxb9K~*05Y@_cy3o)Nkl#@6FyZq)y~Fk5-dj~_U1gAr$-ojAtu8o%hWmy_jGXaT zTxhQR@X=?d`cS6n3Lg~`BxqtGByt6af-hj(9~B)rI$FJ`=UV>{b#s#=qth3?bg9>I zIMFXe(7NlKh~N(*L5PTqi;IcZ%ABi9Dr3)h6wU`a?YB)QN~ z8WtKJ9uXN86*DGrO!T;K`mWan>EdF*I3l7WMFS3 zj!g^S<0i=#0T&~zH*8U2pq zhm4SDK+~v|(J|oi08bg4Ha=r=R`OQI+0yvb#H2)MfY+mh5an)g^xhQ|1NT7QW8>ly znJa1ICuB~ZIXnA($7fmN)5oQa1#?eIOiW0MoVH|fkHe%(jEj#;U>qrVY+73Sgp5hK z1+(KOPW{+%Fe`8Jq=^|5#*ZHd;_o>w;@RFuk2^Mor_q$ev8id}#zS~w)|A58Gqd8c z*+1xbFK^DQnFZ5wCQqK2nUOImcH@Z$yB&=Q8R_YKM#jX+Ia3Q~%_^827l?X^xBFhl zU$olz^qlE4XV0EBBY$fC__q%|Zg-T&Po0vNH*I?1tl6`sPfiZ^m3hgjD$Ykz=QuY` z0(Ru9jZ93NlsjYU9RrQ)9GBl*oSQqds32$J*l3-ve1w-=<;|<2eaM{y1A5{IE+hBy z8X@!baewWnj027f*RSlE=j-7!O5p`6qxAMRs3N>QgTHco8{s)h<*o7=Ick&#F*$B4 zaa=$Z%VFPD>O~a3KHin8$Weqk`Zv2OeO0Ov5{`s)IgH^y^j);u#X`c{cl!_-W|7jQ z^7Zjje{#LQ*4=fKcjUCYnlE2j6nuE#f?${RobTy({N>?5;xW=ksYl(yccjatGgq(g z8Y6+x-p^Jg7Jff);o?QTUD0#l;x)$?&xS*k6)*&)zhWde^JIVjPv(+>Tc_pR`r_5= z*X)kI3m3!{_rm#WjvuSyIX7<~0}~}_?_!4a{&48C(;!Xunz2>KAq!v`N*2ASolJ0d zM^%JZxGNPtZpw!bT^1aQfzvytNnA+SiX)C|7cXGl_v|@;#qncHqPt3=R|YA33T` zJKyMaTsn^x*R98K;I;#DMTJtL^7f(LQXe00m0meY1p{6syDno%#fp30@zVS+yyQ(v z?~!uOJ+JGcqxU@A$!&R$sBjWClu~JG<4=xW`{VaI<`8eI5`*vUN@7hXerV}kCh=}T z-NeVom4vQ%bvkJmZzTA5NQl(COX&i`-af~aoIG#{22isIuUnI&h?WaH$a+AM>&tm77}a>xcUizJ4ZO7kv8qQMJq;D``+*fJUaF zpJDM-T>R5KFCTxWoZ1hzd9}`8%}OxAAv&ElM3FD9pvV65sknl6>XboxwF{F_3;rQu zIYy(62n!1d_r8ZMpS30NVc}!%Wy|LWvED&~f1KzV3PrePu*ywH2rHuFi!66 z9~dx7t}Oeaum2sbI*0~Jf&v3+kc+sC(_n8DB_rZu*=7Z4c{N<&75g(za* z>m4vFQ2`YfdmUSH)vUII;tpGGb@h(x+XACvBErMN!f42d$WbX+LuZc+*Fo_1P*&{3 z(u}nkYc7_UxU6FXe;XebhZQq|)q_GqqEz=?b!_$x!<8m9BuuP-LOMDoJ~1(QeER4= z^!HZ9D7 za}gZ9clt-th}+?GRCKhspr@p!rDqlvPCn%LY*N}dte{Cr2?@}O51;b;`+FQlO?*r& zTX4m-A1mX8%=#ioxN$ERmxPMn;ZKVwFIW{ld) z1C^M)jt>I^W71H8ojG$x!PLB|X>T8V^pe9Elb4&5oj0vu#;h6BCMJdWczTSW@;D`_ zb(~8hBRpk3fno7uGqVeFZ+C2}aa{iM{n?WXXXj^4NFE*J*hWTm6O%f-t_E_X+5O55o$gx;`(-l-&>Dipy?a$lu~FP6{^ z?ow}gc-|ehD_0k4KlpVC{qR9Q;-*r{jB-DjipyjxXo7^`>i20?e8J!f>g8NPmxn@9 zC7^JoR*^!ipC=oO^A}9Xy5$Au`g#6_^|LYtr7IPc@p6BeHyJy)f_~~;K~pM!T|v)d z1+7bWcT>padYP-rMZWl;bN$>t4c!WT_V3ouA8hfia+$#^P~l1Bxl3y;meu#fyAiok z=|=n)4=tY_SUz9b{u0V)29(a^Do;WvHxl=uan}$Mf4BvJ6pV#khtSAihU)~iCVJVsZ#Xl=H+$ifj9&mgy9pck2JNo}a2}G0!vv*vc zM#4UE^!}4t#y`TN< zm*PWe!~Y$Qz;FbHBQP9+|F1{je|Z7GMe={40nz{E_3qpM<@NMQcD?>@$ADe0*?s_J z{Qs;n(0|Qwc`DmLeE#2cdBE}cGy;}L{Ga&tKWhv)jxP0w@ZzQH?ncaYf}cuqym>u2 z-MofyHrr3l_*g|Q*eXbm%|z@r1G!?;<4+*HR!Xi}IWqJ?h{Jl#>aYy`Ua|C=O_odM zD$9BET1z*I@9Z%st96y7(kyA+`Ig}z_s5+F%tnh5cS(q~+Tv<9x4!=AsVckV#}EIw zA9}=EWvMcAuYbADE;;wf_T5cYDBA0-l@>h!4xXyBbKmWJp=mAdv{c|Ex9?OV&TQ{& zT5m45@HUgxXyG=0XSH)5wlp@?n=4@7WMjs-edn9)+>bl9mwA=NWUYXf#L{-W(k?mm;m&fiwB@x=&Q;pUn@xBotEp)X z%o(hFv(d&|xwhjKcJ9+1)UpbWe^+5Ap9t)su5$K4n>N4;cwjR&8*E(r@p6c6F1J*e zxt%|j+sT0zm@``SHqy+u7*YEtUzzOEQ!iVsYw_q9_trU+oxHxuyxLL@8NQioZ53J! z&E$~D&VANywANU-9mk=t19u^+%^(|cId81vj--hQ#tMT+2yKD2Q(N4as!Sie7K;)Z+7TRWLm9(^c zg!B9LxCLgbu&%aT%@wvPtI2`@8SLcLdP_C1^biwT zCAj~>LzEmv%Tc~X579MNZp(Rm-B)g@v6hQ?`c}S;Ywysv(>AixVCT+nrwEeKw#urv zklhS)H9BGfs2-pcvhB1(Xb1Y|XndD%F*WnH8Y^dE>GmB??XI${wF>CC(82?owsRfu z1L&K9e!4|KPM8n_Y9>1%{bdCLZ$RI~003Wt`#VAhZ6|M{_0Md;m^`xfQ+(gN+9JU% zLF6F(p@aC+j*iYwo~eAJw@Xg9wDK@wMDQvtc(!jB)ZWsw=kh9%1SZ(wp-15t?I2yW zlXh_JTOjiR74`HO%2gPkANBAJIc*kGm9vSd@vEi_ZWVPwo9jS`xE*Z*a)ueLW>I?^ zy5}n`mA_bHQE+2kc6Du`n`sx<`5{D3^H4)jp|;jao`(<5Qwv6j*%II-i?$d&+Ue5O zwR!WPD(mFuZO8zpssMMTxS@Csi2L-I-oj8eLEW{zotdRwlFget=oSLoYNz1I$x zkiUF0>u)&*4%!FVq!b2|Y;d5J%x9S1+Od7xwyj&XU<@U@I}mGLbU>VNR-4}H`knp! z^VWkv`D!)XcV-~hHsrs`39@zD_RbyK8PZln?M1|%7ad`vTw#-1ZPu5mUGf(Z=eu%i zr8s-&P#E}5Z_yQ?Y~N99?Glj;b{;TJe+1-sCFYG2WH+4oj;%7zOrdRjyUIBRtUnB7 zTk9se)@4Iu%a-j1JKZeBjXvZNb4ickG$OCbJMia_?PWk6T?KDg0NZ%nW+O80tf=|q;EzR}%)y8VLvz1`H zIcJWce*%2sTcph`&FCcg3Sj$f`X#-akdH0+3DpRvn^Y7c8m zYwOId{1$Grn8xBvrENysg(cg0J6pT}^HCLY7YV{VWPJo~_)bFy`6E1kzWsHB-Sz03ZEfvsdT7F@E|(Td{n}NHorO#Wf=PR%} z9qgcWWFuWes$MdZ_c0aERyP~D4QGM%rP27pD!b(D>($R2O?uMBy3wpBN1?q=ukT)s zruWxv*Ov>+Ia2nyo@PC3~RtriGG&yxrvpr4=VO*tsLt&9r-44VH|^~^K+6IQ$A%pdi( zXR4Ny&sp=DZRmuhS;HB%2l61tUi6a-M}y946nav=f3TzUAkroc^ja|UlxQV z+=k^we7yZK6xuc{S^GHIT4k4hv!{_aJT9$lee3uiA-L=5n#XG%<2IdHZ6`m{PuJKb z-_*Rd9d7M>YRzNo9^p)1H{0EIKl#WbPrmdsoOtuO>PI#_#J%{l85^TD`iCS-U;L4s zUQzYX#s|4){){01{6fWpoRyvZcy+~tbq{b)zfo(K{P6tas~+IYe`TjvnI33ZBzd&` zs~S6ZV13OZuJW&4xaRDqcJh>QQPVxrCpYc;sN637`J-*+wfAt(?d!0+Zo20OyS>eL zPxD>mL4(nJm*k1pe{QwAesouZ-Tt2Ou7h_-?%(ori{0gp`|b8)#yj4>U9!k}%w~7F z{q|#ayYco9Z{ao`v)Em3x#dHAz2&3%T*cu=yW8RU^S`&-A2iJWcrLf%aD!d4Ztk7b z{>@x&@$Oo?^z@x`*HQc5=1LatKE2-V)-iW3gug#JS8`{^>9v?Db6GRlZJhg6ndFX| z@2l-DrPsCZzy4k* z!9Kf$ZMKWW4@!EA1#+brTk&E8u@{?2PjLmgP_&AiDOy8L7p*79iz?YxUW=`~7F&6Z z<|o{6*I+BJ!B$>_t-Jp0v?2^B*0!9>Rg z8+EQ)Z5+}BJN*yp?%JiP)Ead{FdxQ;@*$PDThBeV6FdIy_NkjSYoKfjCLsbEX$aGm z#8rKT)nI>#36BB@xaH6OYGJ$_&=3q4!YFX4i1QT~!Tzb8@Sy^&RJ?;8p$})KXow^@ z;|;Kby4XfdwYCcFyx1N_M+*@GWN0Y&_(>3j!_!){)o_sFHVn>&OWFN!I+}+N4I`b^ zka&ZylGN}23Y_8OqZ_sBb%sGO-W#!E=$NT$OH9Fh&7dh(UyuJt4qHxkg9F$H&D9v0O|H zA1$s~;VU={-RTq_;7dq|DbyGS+z2un;a~vFVyrX{H>7ByJMKxrjo9Bo zg{M&*T*Ek2U^uy?U08X)PFf#U0qZ=J5l^SCYg}A>d_poE!za1oseptDCn32EK@bpe zI@%af9!@$zXLf~!Z3s6-V9?NEQCEl^0bM*fkeD^MHn%?b22R*<)bLaT>E`e=QWF;X%*>m+cV zs04~3N{D7CG>*j61THa=Ci#p>#%;N=sj0YQm*(_o9`<=Z<+ISl$J012Hr4KTSL*t|v84+#NFKFY&XG};>XHR6L<-qzkG>K<$e7qC`4?KOeF-nLe zv(WbPsz`m50X?L#$S$Oq6U&!%5j{*3?>%NrU~uEctw6YzBa4JYL?S+(XC385G<~GF zJ;|;{)X{OE!4yJ~z$f~%kz>6C*0}Wa2@@w~W-`F>f5P)u-#SNAL=^K|03Z@JD%uo9 zZiRn*MHIoj)*~7Cq0S14fkT+0gX1Sm$jF>DNyJUh!$@o#L=}6@g2;wL>PIgunup96M#kYfLlp@ZL8^&IUF(Qq~Y(!$VAqvNc zf;djFo|wodX~l73&)JL{4~H`+PMVaJoilYxUM_>*3xA%coQ-S{p{9cVGc$}$0 z)7*z9Cy!NvPZW@D`ZzFcJE<|m8N^iJlQ@`94s!yFF&2Z2Xk;_!Y54_{XU&*V2nz|sRSIi^TuI!?-BM9~Shh!|kO^)36!(!_Nz@EV_BO4MU^yNVNYEKLn{2E>Ve1AO7k z**V2UbMP!tp}{_Cfj9)PGMJKV%3yJzsodDH0_^Z($lYKN)}-|?^0q$7IEG73h66AF zZ!lrUhorOIL)4u;5W7$~W7eGF5^YL;X-RPrM$QAn!S1zZWzos*gK3KU_H;bQro28? z7)$PjmFASkK;+*}PBx?nFhJAjIDWhq69)KV@SQQ5I&FGE;Y?;>PR3}eatVmZC@g`I zg6Y%qMME^loh25_TAC@v_=vXjFhwe1rZ#mEI`fV`l~2>dKqw4M$jHbP`@lNE#$oET z{OJWVhK!V}NPxjUa@MSwGYSjX;7@U;m)IdX$z6k~W^5%%}be8N!19>!; z%gN!h`7C}i`5UkwnzRYn@98HQCvzCEY?|Z8K%F5egu{S^$62uf>wrrNs7e_SJ1)1h zWYBql(*k#l#k8r26AyfvL*|1|W@mR|css?;WD7Z5ZZ6I911@4MMgrlUJ*T+1q%+!XAfr3dr!4nW+j&l9H5sbm_> z_nAJu0MVVvFpG*x;BTUKlZx0NwWCVq8W5V2F%6HAx)m1{%|XcdneLc%4EQ^QU}Jth zdbUEy*H8BaO5qHKBLcy@(!6o80TvZm3aIWH)d+29N=mNSeLR3l=SXJHehQtvdnU!! z`%o1>gP%!e@v|lHf)=}%U_i1`LIYY=BoihU)T`V=N2iR-%gd*wl9G}S(3jVWi*}&4 zdUSPB5ns%gc$AjT$jeAc4ApK?k#5+uwWzqzC&1k9&C5FtruKbfF-;+fG?WCqBzBPN z)-|iRgl=H%RjKX+f9}@nUQ>|;FsN@Lj~N~mhiHQ z=%KL=UB!79KdsWD z7J$+nx$lKRt-WnqULr z0wBc^*s`r)vx?jYalOs}zp13NwrR(gB2^acKeY}814~pVkOh~oH+qTtW@qr z8~U~ve0ZDV#v*T)I@50RgT%;|wziE-VP(!rd9? zVY3D~%k7Qt5zR57MOTzk?IK~3L;25sui(wZYf8pcogd)AO7LI>c&q|ESOFfa01sAx z2P?pX72v@Nkq4{6gVk%u88sNN8Vpzs2CN1HRzp(_2CN1HRx@1Z2Nf)BEO_)WlD~E3)jbRt=W|_(1SsouH-c@%TU*z&*^^@4lQ) zb%lQgS}ETB)_`W~co9t!n6?k>cjM?rb+vyL+IiMawL%D(E@Y^Nd!!re_k)}k|LOn% z>HyZd;o0^uJeVkei&GudlJ;td?BmNNwfh+T?S5#Z|M~#KpfqrTDM&ISG_~K*@acUBmIz^NZVcM26>MBsw(j=fO}Tyzq2m;rzo;<3$0X%yHGqwX%OvT!SO zR?;wdX8_-4Bg)0$78>_e?0VEm450lKuK{r3;dF?J(`boE@7!Z!WAH3}8c49q6A^e! zm6bvm2&coalXei{om%Z0oe}R7goxz>-ibrNi$TbN6B82)0+`??Qt?=`(LQ1+o?m4T zu!?Xz;8>Lfe;ru-%W8tl#i|7lXHd0Z%-jo4@1rgNZVf`lC%P~;QViKOtO&xR1_I9w zsK1!l;Np7q6&rL490NvU5J&Wek5&R`kY2Sm6b3 z{{x%?aCkYw$=Z{XC&GhoXq>BaL|HXMKwtUra*QY`K;y28P!noDILDk5qO^lyAFOFG zYCAU7Nv{yQ<>>2ENV1q;N+akMN?a)EgpdxvWmKZP!-`wXI}uPL0wVnE?FzhJkqpws zxHj`J<&WT4-wYOPhA>&VhDVQuP=cxhd9DJz9U2IXeYD5WIH16wV)T%4IL5cx3mRDl zyL`Q~Od-0-q9`C;D5`aO*5||6W9?#N%L-)90Mckr28l8QN+@)UiuIV41@P8~$m3Dq z@GQVsv-d?3K(+^ismJi33)Vi<8_48_9E5G1%xb{3;Z85#B1Eu%U> zLH5WEWh-(1>Z42;Co&V=pJ_rhMDNU*5tt6lO%_NxMh(!R7f>?6&lm**o1e5tZ}-@N z1VAMzIawIv>h#-LYC)k)K;=P-vKwQ?ORIk^5wPkH_L)uw8!#9GC%+yRu-X4w6-lP0+z|4y$kIqsf~PTWg3mh~*#}FS2S>M!Y z6K60^G$ju-=|R*_oUHqoszI_Itez(F;M|D+oA#5%aW$~;Dj#bArx!3KnGc}ep<+x# zbdk3X!A~!onKg$oyn^W%&3UXY!3F`;(OoPfAzD1fLKwp(!4jWH?gCM;CNT2-mOjDA z;Ne1Y6zV*`)C@o?L0p}blCf4s_5zcph$PQo56aEW0Vfs>F-y*Z)nGl1rWo0{GxBX4 z^9ZBGNlBH!XWScg24{JOPVjS5V=+WiKqwhKE1H!aPUS9sQR&l*40fMspsN`9A)?Ph z$4_tDFv%sb&Ng4ST&?QNF`k!E6W%_e4@UAdk585qcXjtih z6>hRc)(vvZxTeT7r=;rE0NbN*O!h31cGDT|gZ;=8NiofK7t073Az>ny$;k0yHY>@y zdeCluA~y-Vn`ZejWJZyPiUweuvnEFQtphm8r}6;5sMKsH%|y|KSr9uh1tk$72mCsV z%mrbbJh=m-+%XAV;wST29Qv0-b4M~*5f@xmWZv0hbhUDVDNJ~y+*22qnvHZqbm&aE z2=0uK$h^7S8gRe^IXR3QHyLw;TrMw4zf&yviXxePtMt8gW*tcNQ30=`hns5PBMX45(DMMcF^CyvqiwaUo^ z*qB!@chiL@rRL;JrNxq>B9R-Z>jt?bwnik! za8M(XV_vfzxxgCN$fcTmo{?iK491G&n2+7cx{4j$9!)vgt$X$`LOYMbJ|oB0SVV0e zBgYd=u))YNcKw>YP^S0<9sWl z#=NE#=pr>npENw9#@1Mz<3(zmua`?SJfp$ZSPXlt6`?%ASID`A?A)IG3VEH3i?K3t z%m=NKarcU@Piv7$f|eWvIo>|EM#fFs(}g}-!75|gWYU;>tq76y#2$WHt&B^zf*ju~ z1Jzx!Sw?~k)66nSjP(S_@gCW|cKi0f0*OBXax4pCR-w8^-+ zdq9r&h)pqHGP5AZ_sW7mj-5VAPXx(O1O1z50pyq$MUNa6O@JI9l(A-FJ%$taGID$( zT^4kLxqX~jLQH{nkV%|m=;(y=J9i@J6i(otigbxA9Rl2m^fSyP$T81@9G^%(51VUG zAT#&G^oZt|(7jh=(x7|!d)eD7|M+`F2L38zB-Z%>a*Y42oGUU=Tp7r*4CGh_ax4Qm zmVq40K#paVWU2TLOtuHP;iqWrU;gv6ssFbUe_2~}gUMeHL)M=AzlHv9_0O#h`PJ^= z5&g}qGcV84zjq+yHwgUSN9QjgH+V4SH|@OBk#l2yao}c{#~2Jb5ONcGFDAj@5d7w2 zKa>f75l4Uyef{nKOX0xHivG|Dto`k~{j0^bgxsX$bNX>Yj1coCp9D(5o3`bx%0>cp)j=*pPh9fW>f#C=Y zM_@Pt!x0#cz;FbHBQP9+;Rp;zU^oK95g3lZa0G@UFdTv52n>Yj1coCp z9D(5o3`bx%0>cp)j=*pPh9hue1a9&R5c_ZT`zJU1{hOQp{t>yU1ArIMZua|U_W=r#HueUtWJ@LAc59AHxHB?zfx;|MUH87J}c- XgL58J#8OhVga#L(5nD(kk@aSg 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 E55lP6v;Y7A literal 0 HcmV?d00001 diff --git a/inspectIT/icons/selfmade/inspectIT.xpm b/inspectIT/icons/selfmade/inspectIT.xpm new file mode 100644 index 000000000..9352e4d9e --- /dev/null +++ b/inspectIT/icons/selfmade/inspectIT.xpm @@ -0,0 +1,488 @@ +/* XPM */ +static char * 48x48_xpm[] = { +"48 48 437 2", +" c None", +". c #9F1C17", +"+ c #9E1C17", +"@ c #9D1C17", +"# c #9D1C16", +"$ c #9C1D17", +"% c #9C1C16", +"& c #9D1F19", +"* c #9D1E18", +"= c #9D1F1A", +"- c #9C1F19", +"; c #9D1E19", +"> c #9C1E19", +", c #9C1E18", +"' c #9C1D18", +") c #AC4541", +"! c #C88986", +"~ c #DFBFBD", +"{ c #EADAD9", +"] c #F5F5F5", +"^ c #D4A4A1", +"/ c #BD6D6A", +"( c #A12924", +"_ c #9B1C16", +": c #9B1C17", +"< c #9E221D", +"[ c #9E211C", +"} c #9D211C", +"| c #9D201B", +"1 c #9C1F1A", +"2 c #B25450", +"3 c #DFBFBE", +"4 c #EFE7E7", +"5 c #A12925", +"6 c #9C1D19", +"7 c #9F2520", +"8 c #9E2420", +"9 c #9E241F", +"0 c #9E231F", +"a c #9E231E", +"b c #9D221D", +"c c #C98B88", +"d c #E4CCCB", +"e c #9B1D18", +"f c #9C201B", +"g c #9E221E", +"h c #A02824", +"i c #9F2823", +"j c #9F2722", +"k c #9F2621", +"l c #9E2520", +"m c #D4A6A5", +"n c #F0E8E8", +"o c #CE9996", +"p c #AD4945", +"q c #A12C28", +"r c #BD6F6C", +"s c #DFC0BE", +"t c #EFE8E7", +"u c #AC4642", +"v c #9B1E19", +"w c #9C211C", +"x c #9D221E", +"y c #9D231E", +"z c #9D241F", +"A c #9F2622", +"B c #A12C27", +"C c #A12B27", +"D c #A02A26", +"E c #A02924", +"F c #9F2824", +"G c #CF9B99", +"H c #DAB4B3", +"I c #A3312D", +"J c #B86460", +"K c #A73A36", +"L c #9D231F", +"M c #9E2621", +"N c #A02925", +"O c #A2302C", +"P c #A12F2B", +"Q c #A12E2A", +"R c #A12D29", +"S c #B55E5B", +"T c #C58380", +"U c #9E2823", +"V c #9E2722", +"W c #9D2520", +"X c #9D2420", +"Y c #9C231F", +"Z c #A73D39", +"` c #E4CECD", +" . c #DFC0BF", +".. c #9C221E", +"+. c #9C231E", +"@. c #9C241F", +"#. c #9D2621", +"$. c #9F2924", +"%. c #9F2925", +"&. c #9F2B26", +"*. c #A02C27", +"=. c #A12F2A", +"-. c #A43530", +";. c #A3332F", +">. c #A3322D", +",. c #A2312C", +"'. c #EBDCDC", +"). c #D6AAA9", +"!. c #A02C28", +"~. c #A02B27", +"{. c #9F2A25", +"]. c #AE4D49", +"^. c #BE7370", +"/. c #9F2A26", +"(. c #A12E29", +"_. c #A3332E", +":. c #A43833", +"<. c #A43732", +"[. c #A33530", +"}. c #BC706D", +"|. c #EADCDC", +"1. c #A63B37", +"2. c #A02E29", +"3. c #A02D28", +"4. c #9F2D27", +"5. c #9E2924", +"6. c #9E2923", +"7. c #9D2822", +"8. c #9D2721", +"9. c #9D2620", +"0. c #C3807D", +"a. c #9C2620", +"b. c #9C2520", +"c. c #9D2722", +"d. c #9F2C27", +"e. c #A1312C", +"f. c #A2332D", +"g. c #A53B36", +"h. c #A53934", +"i. c #A43733", +"j. c #D6ADAC", +"k. c #C17B78", +"l. c #A1302C", +"m. c #A02D29", +"n. c #9F2D28", +"o. c #9F2C26", +"p. c #9E2A25", +"q. c #9E2925", +"r. c #9E2824", +"s. c #9D2723", +"t. c #EADBDB", +"u. c #A8403B", +"v. c #9C2621", +"w. c #9D2622", +"x. c #9D2823", +"y. c #A02E2A", +"z. c #A2322D", +"A. c #A3342F", +"B. c #A33531", +"C. c #A53A35", +"D. c #A8403D", +"E. c #A73E3B", +"F. c #A63D39", +"G. c #EBDEDD", +"H. c #A8403E", +"I. c #A23430", +"J. c #A2332F", +"K. c #A1312E", +"L. c #A0302D", +"M. c #A02E2B", +"N. c #9F2D2A", +"O. c #9F2C28", +"P. c #9F2C29", +"Q. c #9E2B27", +"R. c #9E2A26", +"S. c #9E2926", +"T. c #9D2825", +"U. c #9D2724", +"V. c #CF9B9A", +"W. c #BE7472", +"X. c #9D2925", +"Y. c #9F2D29", +"Z. c #A02F2C", +"`. c #A1302D", +" + c #A1322F", +".+ c #A33633", +"++ c #A43734", +"@+ c #A53A37", +"#+ c #A63D3A", +"$+ c #A7403D", +"%+ c #AA4A48", +"&+ c #A94745", +"*+ c #A84442", +"=+ c #E6D2D2", +"-+ c #A43C39", +";+ c #A43A37", +">+ c #A33836", +",+ c #A23634", +"'+ c #A13532", +")+ c #A03330", +"!+ c #A0322F", +"~+ c #A0312F", +"{+ c #9F302E", +"]+ c #9F302D", +"^+ c #9F2F2D", +"/+ c #9E2E2C", +"(+ c #9E2E2B", +"_+ c #9E2D2A", +":+ c #A84543", +"<+ c #C9908E", +"[+ c #B96B69", +"}+ c #C9908F", +"|+ c #9D2B28", +"1+ c #9D2B29", +"2+ c #9D2C2A", +"3+ c #9F2F2C", +"4+ c #A03230", +"5+ c #A13431", +"6+ c #A33835", +"7+ c #A43A38", +"8+ c #A43D3A", +"9+ c #A6403E", +"0+ c #A74240", +"a+ c #AA4947", +"b+ c #9C2D2A", +"c+ c #A5403E", +"d+ c #AB4F4D", +"e+ c #F4F4F4", +"f+ c #E1C9C8", +"g+ c #A64442", +"h+ c #A54240", +"i+ c #A4403E", +"j+ c #A43E3C", +"k+ c #A33D3A", +"l+ c #A23B38", +"m+ c #A13936", +"n+ c #A13835", +"o+ c #A03734", +"p+ c #A03533", +"q+ c #A03532", +"r+ c #9F3431", +"s+ c #9F3330", +"t+ c #9E3230", +"u+ c #CA9492", +"v+ c #C48786", +"w+ c #D4ABAA", +"x+ c #9E312F", +"y+ c #9E322F", +"z+ c #9F3532", +"A+ c #A03634", +"B+ c #A13937", +"C+ c #A64341", +"D+ c #A74644", +"E+ c #A94947", +"F+ c #AA4D4B", +"G+ c #A84846", +"H+ c #8D100D", +"I+ c #8E1411", +"J+ c #E1CBCA", +"K+ c #A9504E", +"L+ c #A84D4B", +"M+ c #A74B49", +"N+ c #A64846", +"O+ c #A54644", +"P+ c #A44341", +"Q+ c #A3413F", +"R+ c #A2403E", +"S+ c #A23F3C", +"T+ c #A13E3B", +"U+ c #A13D3A", +"V+ c #A03C39", +"W+ c #A03B39", +"X+ c #9F3A37", +"Y+ c #CA9796", +"Z+ c #B56866", +"`+ c #D5AFAE", +" @ c #9F3836", +".@ c #9F3936", +"+@ c #A03A38", +"@@ c #A3423F", +"#@ c #A44442", +"$@ c #A94E4C", +"%@ c #A44543", +"&@ c #9A2D2A", +"*@ c #901816", +"=@ c #8A100D", +"-@ c #E8D8D8", +";@ c #8C1411", +">@ c #92211E", +",@ c #99312E", +"'@ c #9F3C3A", +")@ c #A44745", +"!@ c #A74D4B", +"~@ c #A64B49", +"{@ c #A54B48", +"]@ c #A54A48", +"^@ c #A44946", +"/@ c #A34644", +"(@ c #A34442", +"_@ c #A24341", +":@ c #A14240", +"<@ c #E5D3D3", +"[@ c #EBDFDF", +"}@ c #BB7978", +"|@ c #CB9B9A", +"1@ c #A1403E", +"2@ c #A1413E", +"3@ c #A1413F", +"4@ c #A24441", +"5@ c #A34542", +"6@ c #A44846", +"7@ c #A64D4B", +"8@ c #A8504D", +"9@ c #9D3936", +"0@ c #962926", +"a@ c #8E1916", +"b@ c #87100D", +"c@ c #E7D8D8", +"d@ c #952D2A", +"e@ c #891411", +"f@ c #8F211E", +"g@ c #952D2B", +"h@ c #97312F", +"i@ c #9B3936", +"j@ c #9E403E", +"k@ c #9E3F3D", +"l@ c #A54F4D", +"m@ c #A54E4C", +"n@ c #B87674", +"o@ c #D1ABAA", +"p@ c #D6B5B4", +"q@ c #E5D4D4", +"r@ c #B77473", +"s@ c #A34B49", +"t@ c #A44D4B", +"u@ c #A6504E", +"v@ c #A24846", +"w@ c #9B3937", +"x@ c #85100D", +"y@ c #CB9F9E", +"z@ c #BD8381", +"A@ c #E0CAC9", +"B@ c #A04845", +"C@ c #F0EBEB", +"D@ c #EBE1E0", +"E@ c #B77775", +"F@ c #8B1D1A", +"G@ c #82100D", +"H@ c #9E4947", +"I@ c #EEE7E7", +"J@ c #891E1C", +"K@ c #CA9F9E", +"L@ c #BB8381", +"M@ c #AD6664", +"N@ c #7E100D", +"O@ c #D7BCBB", +"P@ c #D0ADAD", +"Q@ c #8D2D2A", +"R@ c #D8BCBB", +"S@ c #C89F9E", +"T@ c #953B39", +"U@ c #BA8381", +"V@ c #861E1C", +"W@ c #7A100D", +"X@ c #913B39", +"Y@ c #C79F9E", +"Z@ c #8A2D2A", +"`@ c #994947", +" # c #A96664", +".# c #BF9190", +"+# c #923B39", +"@# c #EDE7E7", +"## c #B88381", +"$# c #77100D", +"%# c #AE7473", +"&# c #D6BCBB", +"*# c #872D2A", +"=# c #D5BCBB", +"-# c #CDADAD", +";# c #964947", +"># c #C69F9E", +",# c #72100D", +"'# c #C49F9E", +")# c #C59F9E", +"!# c #9C5856", +"~# c #934947", +"{# c #924947", +"]# c #944947", +"^# c #AC7473", +"/# c #E5D8D8", +"(# c #8B3B39", +"_# c #6E100D", +":# c #A06664", +"<# c #ECE7E7", +"[# c #D3BCBB", +"}# c #975856", +"|# c #904947", +"1# c #69100D", +"2# c #7A2C2A", +"3# c #C09F9E", +"4# c #E3D8D8", +"5# c #9D6664", +"6# c #D2BCBB", +"7# c #955856", +"8# c #630F0D", +"9# c #6C1D1C", +"0# c #996564", +"a# c #BE9F9E", +"b# c #D0BCBB", +"c# c #AB8281", +"d# c #7E3A39", +"e# c #752C2A", +"f# c #5D0F0C", +"g# c #661D1B", +"h# c #A88281", +"i# c #EBE7E6", +"j# c #A98281", +"k# c #570F0C", +"l# c #601D1B", +"m# c #9B7472", +"n# c #E1D8D8", +"o# c #885755", +"p# c #550F0C", +"q# c #916563", +"r# c #B99F9E", +"s# c #540F0C", +"t# c #865755", +"u# c #CDBCBB", +"v# c #D7CAC9", +"w# c #7C4946", +"x# c #5E1D1B", +"y# c #682C29", +"z# c #AF908F", +"A# c #530F0C", +"B# c #520F0C", +"C# c #510F0C", +"D# c #500E0C", +" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ", +" . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ", +" + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + ", +" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", +"+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", +"@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ", +"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ", +"$ $ $ $ $ % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % ", +"& * * * * $ $ $ $ $ $ % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % $ $ $ ", +"= = & - ; > > , ' ' ) ! ~ { ] ] ~ ^ / ( _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ : : : $ $ ' ' , ; ", +"< < [ } | = 1 > 2 3 ] ] ] ] ] ] ] ] ] 4 ! 5 : : : : : : : : : : : : : : : : : ' ' 6 > > > > = | ", +"7 8 9 0 a b } c ] ] ] ] ] ] ] ] ] ] ] ] ] d ) e e e e e e e e e e e e > > > > > > > 1 f } b g a ", +"h i j k l 8 m ] ] ] n o p f 1 1 q r s ] ] ] t u v v v v v v v v v v v v 1 1 f f w } x y z 9 l A ", +"B C D E F G ] ] ] H I z y x b b b b w J n ] ] t K f f f f f f f f w w w b x y L 9 9 l M j i N D ", +"O P Q R S ] ] ] T U U V M W W W X z z Y Z ` ] ] ...+.....+.+.+.+.@.@.z X W #.V V U $.%.&.*.R =.", +"-.;.>.,.'.] ] ).!.~.D {.$.i U U V V V M #.].n ] ] ^.W W W W W l M M M V U $.%.{./.~.!.(.=.O >._.", +":.<.[.}.] ] |.1.2.3.4.&.{.5.5.6.U 7.8.8.#.9.0.] ] ` a.b.b.#.#.8.8.c.c.U U 5.{.&.d.3.2.P e.f.[.<.", +"g.h.i.j.] ] k.l.=.m.n.o.&.p.p.q.5.r.s.s.c.#.a.t.] ] u.#.v.#.c.w.w.s.x.q.q.&.d.4.m.y.l.z.A.B.i.C.", +"D.E.F.G.] ] H.I.J.K.L.M.N.O.P.Q.Q.R.S.T.T.U.U.V.] ] W.U.U.T.T.X.S.R.R.Q.O.Y.M.Z.`. +I..+++@+#+$+", +"%+&+*+] ] =+-+;+>+,+'+)+!+~+~+{+]+^+/+(+_+:+<+[+] ] }+|+1+2+_+_+(+/+3+{+~+4+5+'+,+6+7+8+9+0+:+a+", +"b+c+d+e+] f+g+h+i+j+k+l+m+n+n+o+p+q+r+s+t+u+] v+] ] w+x+y+t+s+r+r+z+q+A+n+B+l+k+j+i+C+D+E+F+G+p+", +"H+H+I+] ] J+K+L+M+N+O+P+Q+R+R+S+T+U+V+W+X+Y+] Z+] ] `+ @.@X+X++@W+U+U+S+R+@@#@O+N+M+$@%@&@*@H+H+", +"=@=@=@] ] -@;@>@,@'@)@!@~@{@]@^@)@/@(@_@:@<@[@}@] ] |@1@2@3@_@_@4@5@/@6@]@7@8@(@9@0@a@=@=@=@=@=@", +"b@b@b@c@] ] d@b@b@b@b@e@f@g@h@i@j@k@l@m@n@e+o@p@e+e+q@r@s@t@t@l@u@v@j@w@g@f@b@b@b@b@b@b@b@b@b@b@", +"x@x@x@y@] ] z@x@x@x@x@x@x@x@x@x@x@x@x@x@A@] B@C@] ] ] ] D@E@F@x@x@x@x@x@x@x@x@x@x@x@x@x@x@x@x@x@", +"G@G@G@H@] ] I@J@G@G@G@G@G@G@G@G@G@G@G@K@] L@L@] ] ] ] ] ] ] c@M@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@G@", +"N@N@N@N@O@] ] P@N@N@N@N@N@N@N@N@N@Q@R@] S@T@] ] ] ] ] ] ] ] ] ] I@U@V@N@N@N@N@N@N@N@N@N@N@N@N@N@", +"W@W@W@W@X@] ] ] Y@W@W@W@W@Z@`@ #S@] ] .#+#@#] ] ] ] ] ] ] ] ] ] ] ] @###Z@W@W@W@W@W@W@W@W@W@W@W@", +"$#$#$#$#$#%#] ] ] &#*#$#$#=#] ] ] -#;#%#@#] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] >#*#$#$#$#$#$#$#$#$#$#", +",#,#,#,#,#,#'#] ] ] ] )#!#~#{#{#]#^#/#] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] )#(#,#,#,#,#,#,#,#", +"_#_#_#_#_#_#_#:#<#] ] ] ] ] ] ] ] ] ] ] ] [#}#[#] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] [#|#_#_#_#_#_#", +"1#1#1#1#1#1#1#1#2#3#] ] ] ] ] ] ] ] ] 4#5#1#1#1#2#3#] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] 6#7#1#1#1#", +"8#8#8#8#8#8#8#8#8#8#9#0#a#b#b#b#b#c#d#8#8#8#8#8#8#8#e#a#<#] ] ] ] ] ] ] ] ] ] ] ] ] ] a#4#8#8#8#", +"f#f#f#f#f#f#f#f#f#f#f#f#f#f#f#f#f#f#f#f#f#f#f#f#f#f#f#f#g#h#i#] ] ] ] ] ] ] ] ] ] ] ] j#] f#f#f#", +"k#k#k#k#k#k#k#k#k#k#k#k#k#k#k#k#k#k#k#k#k#k#k#k#k#k#k#k#k#k#l#m#n#] ] ] ] ] ] ] ] ] ] o#] k#k#k#", +"p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#p#q#n#] ] ] ] ] ] ] r#r#] p#p#p#", +"s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#t#u#] ] ] ] v#m#] r#s#s#s#", +"s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#w#u#] ] u#] v#x#s#s#s#", +"s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#s#y#r#i#z#x#s#s#s#s#", +"A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#", +"A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#", +"A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#A#", +"B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#", +"B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#", +" B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B#B# ", +" C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C# ", +" C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C#C# ", +" D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D#D# "}; diff --git a/inspectIT/icons/selfmade/parameter.png b/inspectIT/icons/selfmade/parameter.png new file mode 100644 index 0000000000000000000000000000000000000000..8c350ce2e6dda093be431d138cfe662d940ec40d GIT binary patch literal 900 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 literal 0 HcmV?d00001 diff --git a/inspectIT/icons/selfmade/server_online_16x16.png b/inspectIT/icons/selfmade/server_online_16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..19479db2261ef062d4154750133f9b1f2f09cbaf GIT binary patch literal 981 zcmV;`11kK9P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iph= z5DgqwX>6MS00UJ?L_t(I%T?1!Y?O5z#_{jFbf&Y@+EThPO!sQjC3^9|5+o$vFdE?| z7wdv>FeVaBh`_-_&;*Pzsm4%=(Y8Sh1dOqj6#L-}!kyc`iSRb@$GRvj<{L?Om0RR5tnj{+P$(X?7gP(X{LjQU>QYLdP^MkQ{n@+c|RNHB8gOG989TzM^V#eO&#otX95Taq;io z%dwU%tvt}DxpeakeW`uaD{Z(~m_oz0VKpzXOq)A*iNB9R~n6 z0y`RZaQ@aHV$32MYGGw1Nh+PgbtSSnNF*|trno2bIxipC`RuktF)nGP6j~`w+D_12 z(Zk0(KjFvCgD7qpLfE8ItE{YO*d>ennohw;Ey&f)fv0PH)$YMqKc;N3N|N0*dx-^` zQDud)sDUUI5W>P+9wwVJab1a=mL;ECyr%|zzCg(9m;7pg7aCt>p*Tltpp>Dfp2 zSY@qYX$bLJh& z*MfxADrCjO@X+U6xqKdv*H1_1-VA_}TQq)e-_!V}91*UnAZ=2sIF~z#g9EaQH1-&k zb{*5xlPoPSQQO@{$F@CgPj~y|==T>sQ%F)+s7zjqZmm98*AOA2EV^NJ6N!968u|9S zfhT64FREV8R<$fhZEchLc*m1cELM~Ibl|=1eSO>Y_0Lq^`C;wh*0wF3>FiqO`i0pq z7~xNV6DJ4f8yXvGz@5HzbMnj6r`|ZTfBzwdhCbq7e{h~hNuday00000NkvXXu0mjf DN4?2< literal 0 HcmV?d00001 diff --git a/inspectIT/icons/selfmade/server_refresh.png b/inspectIT/icons/selfmade/server_refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..f1652f8fbcd52210b8b222d34c619348572ad3e0 GIT binary patch literal 2584 zcmV+z3g`8SP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipS= z2{{is1cz<_0122$L_t(o!=0B|Y+P3v$A9zZM#7k;tX`Il|BqY!nXxc&v z4Qn^rLRkbnBKm+p0#S-oAqqk)5)Z%wq9P4RN=$)tQ7LiS(l&L{C21SS@iJq3#va@8 z>@#;e=kVZU+?2Qlj&yaTd+)jb-}#^Kp7VVg@6vYf{;URc0KGoL=vlY!s-BL{j-Gf^ zb8kaKLk}o3J)M5`<ȣ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 literal 0 HcmV?d00001 diff --git a/inspectIT/icons/selfmade/server_remove.png b/inspectIT/icons/selfmade/server_remove.png new file mode 100644 index 0000000000000000000000000000000000000000..db53f3c30bbff8d67835277af5b45bb5c54c9dbf GIT binary patch literal 1562 zcmV+#2IcvQP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iOG~ z2Nf*E-;f&s00o*!L_t(Y$BmZTZ(K(m$3JuSoIT$4?i%mQq>m(M8Xo{ALtVgjf8lC3L%~_JYYdZE+EpSih|l&sT0RGAs1phws-Bl z*te{OGYCl+sh);zS< z{_ViRvDRn=Wn^28$=HnAhD{3T@J@sE&BMgC(dzT^v2qEb1 z>887%9s1yYp6Oc;m&~VW>-#=ZYNV9NP}V_P+Z6Iu5(%4@ zmS&>1MJgGkIbmW&4W!hRiW@906!`btJo$WyQn~Up0C=8H#87pBZ1lUJ)zu18x;Uu$KU4O-Tx4VA&#>}rM!mY zRO#wWGCX`3ce{)A^{uBwfW~_aprj-WccZDV+TqUZ6-Gy2Cy~gK%^v6A!Gpx(&8*~$ zT)Om6JkNX*z$X&borj^S0~(-41dc;GlV)^u6s0sl5F({wbJJmSqlV*zNEz&vXbjT& z+Pg1r2J0m3dg3+$1Hb zvb?l})&^Spb->Ty8!sL^_Lnc6JsTB55SGh(RO=iX;+tJv{QXzIp!o8Sh$K^Jt*MkN zR4SWDsSt)i7)EeBO@6hA)}kh0-57&ZUpoDp!zWHeWu<~FmyzW%YNu~BBMA7)$V(WP ze+#-^Z9(9pF_22&d79O=4c6DI%s;$GQ&YSMIA#-^KG@&Ck6I12v*n20eAi{#d5*WV zuy6hri`6xB)Iw`PskDjbx*!Z)zr5 zp#1U2EIgb+9Qxu;a-liditA~%wmeGZB}%1LY&*LIV8&p-sWs8IHa?BTV=>H15v?>* zY04E3$EgsFTBI{+1`Z72`x48Nj{r;q+z6lEB7in6w62$DG#Vk@){kj6v9x@Tciy=` z5J36zL&7H?_6+BxRL z&gw%TlF48K)s+<<9y>+Ew9t;`>&M?J`UBsMsRxpR;-0H*J=HZ`TMIqrq!OxMUh&6l;-Se8v&TQ2~XWwCF2kk&6G zyxi5lzLCpKlo~nXU*I0l`uC6W`KVDWzL1JU>}JzMxh{o;1txFYc#ta=e^}A_!ku*H zVmzLDy|cTkrKKfBPj4584h`|#bAuF%OOv_xE{=|m|MG9oyj6@rs|`mJ&@aK?gc%7R zz70WR%*m5yhrjx@=JXaE2J literal 0 HcmV?d00001 diff --git a/inspectIT/inspectIT.product b/inspectIT/inspectIT.product new file mode 100644 index 000000000..0990e9295 --- /dev/null +++ b/inspectIT/inspectIT.product @@ -0,0 +1,185 @@ + + + + + + + + + %aboutText + + + + + + + + -Xmx1024M -Xms128m + + + + + + + + + + + + + + + + + + + + + + http://www.inspectit.eu + + 1. SCOPE OF APPLICATION +These General Terms and Conditions of Use apply to any licensing of the product. The application of any contradicting purchasing terms and conditions by the Licensee is excluded, even if the Licensor does not expressly exclude their application in individual cases. +By applying for a license key, or downloading the product, or installing the product, the Licensee agrees to be fully bound by these General Terms and Conditions of Use. + +2. GRANT OF LICENSE +The license is granted by reception of a license key the licensee has requested by registering on the Licensor´s website. The licensor reserves the right to reject any application for a license key without citing any reasons. The license is granted only to named users who have registered on the Licensor´s website. Each user may request only one license key. It is prohibited to use the license key of any other user. +If the license is granted commercially or on behalf of a commercial third party, the registration must include the name of the respective company. In this case, the named company is deemed to be the licensee, not the user. +The License includes and is limited to the following rights to use the product: +Use for performance analysis purposes and all other test purposes in commercial and non commercial software development projects. +The right to use the product is time-limited and expires within three months after reception of the license key. +The product may be licensed repeatedly under the then current General Terms and Conditions of Use. +The license is not limited to any region or territory. It is non-assignable in full or in part. The Licensor reserves the right to charge an appropriate license fee for all later versions of the product. +Any use on productive systems or for productive purposes requires the Licensor´s prior consent. If the Licensor consents, the licensee will allow the licensor an on-site reference visit in order to observe the productive use of the product and potentially gain knowledge for its development. Furthermore, to licensee +will allow the licensor to publish a success story which will be coordinated by both parties. +The Licensor´s consent is conditional on the conclusion of an individual agreement between the parties. +The Licensor retains all rights to the product (including but not limited to ownership and copyright) unless said rights are expressly granted to the Licensee by these terms and conditions. + +3. ADDITIONAL SOFTWARE AND SERVICES +These General Terms and Conditions of Use also apply to updates, expansions, add-on components or components of online services for the product which the Licensor may provide or make available. The Licensor has no such obligation. The Licensor has no obligation to provide any maintenance or support services. +The Licensor reserves the right to discontinue to product at any time without notice. + +4. LIMITATIONS WITH REGARD TO REVERSE ENGINEERING, DECOMPILING AND DISASSEMBLY +The Licensee is not entitled to reverse engineer, decompile or disassemble the product unless (and to the extent) expressly permitted by applicable law irrespective of this limitation. + +5. WARRANTY, LIABILITY, AND THIRD-PARTY RIGHTS +With respect to the product being free of charge, the Licensor makes no warranties regarding the product whatsoever and shall under no circumstances be liable for any damages whatsoever including third party claims that may arise from the use of the product. + +6. OVERALL REGULATION OF THE CONTRACTUAL ARRANGEMENT +These General Terms and Conditions of Use (including all separate addenda or supplementary agreements) govern the entire contractual arrangement between the Licensor and the Licensee in connection with the product and any support services. They take precedence over any previous or concurrent, verbal or written +notices, proposals or commitments with regard to the product or any other subject of the license. If individual provisions are contradicted by provisions of one of the Licensor’s policies or one of his programs for support services, these General Terms and Conditions of Use shall take precedence. + +7. TERMINATION +Without prejudice to other rights, the Licensor is entitled to terminate this agreement without notice if the Licensee does not observe the provisions of this agreement. In this case, the Licensee is obliged to destroy all copies of the product and all of its components. +The Licensee shall ensure that the software governed by the agreement is used in accordance with the provisions of the agreement. If there is a justified suspicion that the software is being used in breach of the provisions of the agreement, the Licensor shall be entitled to verify the proper use of the software using +appropriate measures, including on-site inspection if necessary. + +8. REQUIREMENT OF THE WRITTEN FORM +All amendments, addenda and supplements to these General Terms and Conditions of Use must be in writing. Any agreement revoking or limiting this requirement of the written form must also be in writing. + +9. APPLICABLE LAW AND COURT OF COMPETENT JURISDICTION +The validity of these General Terms and Conditions of Use and any legal consequences resulting from them are to be assessed in accordance with the law of the Federal Republic of Germany subject to the exclusion of standards governing conflicting laws and the United Nations Convention on Contracts for the International +Sale of Goods. The court of competent jurisdiction is Stuttgart. + +NTSales_Allgemeine_Nutzungsbedingungen_inspectIT_en.doc_V2.0 / 24.07.2012 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inspectIT/inspectIT.target b/inspectIT/inspectIT.target new file mode 100644 index 000000000..075e5c40d --- /dev/null +++ b/inspectIT/inspectIT.target @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/inspectIT/logging-config.xml b/inspectIT/logging-config.xml new file mode 100644 index 000000000..df2eab73b --- /dev/null +++ b/inspectIT/logging-config.xml @@ -0,0 +1,76 @@ + + + + + + + System.out + + %d{ISO8601}: %-6r [%15.15t] %-5p %30.30c - %m%n%rEx + + + + + + + logs/inspectit.log + + %d{ISO8601}: %-6r [%15.15t] %-5p %30.30c - %m%n%rEx + + + logs/inspectit.%d{yyyy-MM-dd}.%i.zip + 30 + + 20MB + + + + + + + + + logs/exceptions.log + + %d{ISO8601}: %-6r [%15.15t] %-5p %30.30c - %m%n%rEx + + + logs/exceptions.%d{yyyy-MM-dd}.%i.zip + 30 + + 20MB + + + + WARN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/inspectIT/plugin.properties b/inspectIT/plugin.properties new file mode 100644 index 000000000..6ca4a68a3 --- /dev/null +++ b/inspectIT/plugin.properties @@ -0,0 +1 @@ +aboutText= inspectIT - Application Performance Engineering solution\nVisit us on http://inspectit.rocks/\n\nCopyright (c) 2008- NovaTec Consulting GmbH. All rights reserved.\nThe inspectIT icon and logo are trademark of NovaTec Consulting GmbH. Other names may be trademarks of\ntheir respective owners.\n\nThis product includes software developed by other open source projects including the Eclipse Foundation,\nthe Apache Foundation and the Free Software Foundation. For a detailed listing of the third party libraries\nused in inspectIT please check the THIRDPARTYLICENSE.txt that is contained in the root distribution/installation\nfolder. \n\n\n\Version: 1.5\n\Build ID: 201311221014 \ No newline at end of file diff --git a/inspectIT/plugin.xml b/inspectIT/plugin.xml new file mode 100644 index 000000000..73902b53a --- /dev/null +++ b/inspectIT/plugin.xml @@ -0,0 +1,2440 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create Storage + + + + + + + + + + + + + + + + + Add Central Management Repository (CMR) + + + + + + + + + Import Storage + + + + + + + Export Storage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inspectIT/plugin_customization.ini b/inspectIT/plugin_customization.ini new file mode 100644 index 000000000..93df9005d --- /dev/null +++ b/inspectIT/plugin_customization.ini @@ -0,0 +1,2 @@ +org.eclipse.ui/SHOW_PROGRESS_ON_STARTUP = true +org.eclipse.ui/SHOW_TRADITIONAL_STYLE_TABS=false \ No newline at end of file diff --git a/inspectIT/release/build.properties b/inspectIT/release/build.properties new file mode 100644 index 000000000..938ec29a5 --- /dev/null +++ b/inspectIT/release/build.properties @@ -0,0 +1,338 @@ +#################################### +# Properties for main build script +#################################### + +#eclipseLocation=/opt/eclipse +eclipseLocation=${release.basedir}/runtime/base/eclipse + + +#equinoxLauncherPluginVersion=1.0.101.R34x_v20080819 +#equinoxLauncherPluginVersion=1.0.201.R35x_v20090715 +#pdeBuildPluginVersion=3.4.1.R34x_v20080805 +#pdeBuildPluginVersion=3.5.1.R35x_20090820 + +equinoxLauncherPluginVersion=1.3.0.v20120522-1813 +pdeBuildPluginVersion=3.8.2.v20121114-140810 + + +############################################################################### +# 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 +############################################################################### +##################### +# Parameters describing how and where to execute the build. +# Typical users need only update the following properties: +# baseLocation - where things you are building against are installed +# bootclasspath - The base jars to compile against (typicaly rt.jar) +# configs - the list of {os, ws, arch} configurations to build. +# +# Of course any of the settings here can be overridden by spec'ing +# them on the command line (e.g., -DbaseLocation=d:/eclipse + +#The type of the top level element we are building, generally "feature" +topLevelElementType = plugin +#The id of the top level element we are building +#topLevelElementId = info.novatec.inspectit.rcp +topLevelElementId = info.novatec.inspectit.rcp + +############# PRODUCT/PACKAGING CONTROL ############# +#product=/plugin or feature ${eclipse.home}/NovaSpy/rcp.product +#product=/home/novaspy/eclipse-workspace/NovaSpy/rcp.product +product=../inspectIT.product + +runPackager=true + +#Set the name of the archive that will result from the product build. +#archiveNamePrefix= + +# The prefix that will be used in the generated archive. +#archivePrefix=eclipse +archivePrefix=inspectit + +# The location underwhich all of the build output will be collected. +collectingFolder=${archivePrefix} + +# The list of {os, ws, arch} configurations to build. This +# value is a '&' separated list of ',' separate triples. For example, +# configs=win32,win32,x86 & linux,motif,x86 +# By default the value is *,*,* +#configs = *, *, * +#configs = linux, gtk, x86 +configs=win32, win32, x86 & \ + win32, win32, x86_64 & \ + linux, gtk, x86 & \ + linux, gtk, x86_64 & \ + macosx, cocoa, x86 & \ + macosx, cocoa, x86_64 + +# MacOS correct version codes +# macosx, cocoa, x86 & \ +# macosx, cocoa, x86_64 & \ + +# Old version codes +# linux, gtk, ppc &\ +# linux, motif, x86 & \ +# solaris, motif, sparc & \ +# solaris, gtk, sparc & \ +# aix, motif, ppc & \ +# hpux, motif, PA_RISC & \ +# macosx, carbon, ppc + +# By default PDE creates one archive (result) per entry listed in the configs property. +# Setting this value to true will cause PDE to only create one output containing all +# artifacts for all the platforms listed in the configs property. +# To control the output format for the group, add a "group, group, group - " entry to the +# archivesFormat. +#groupConfigurations=true + +#The format of the archive. By default a zip is created using antZip. +#The list can only contain the configuration for which the desired format is different than zip. +#archivesFormat=win32, win32, x86 - antZip& \ +# linux, gtk, ppc - antZip &\ +# linux, gtk, x86 - antZip& \ +# linux, gtk, x86_64 - antZip& \ +# linux, motif, x86 - antZip& \ +# solaris, motif, sparc - antZip& \ +# solaris, gtk, sparc - antZip& \ +# aix, motif, ppc - antZip& \ +# hpux, motif, PA_RISC - antZip& \ +# macosx, carbon, ppc - antZip + +#Set to true if you want the output to be ready for an update jar (no site.xml generated) +#outputUpdateJars = false + +#Set to true for Jnlp generation +#codebase should be a URL that will be used as the root of all relative URLs in the output. +#generateJnlp=false +#jnlp.codebase= +#jnlp.j2se= +#jnlp.locale= +#jnlp.generateOfflineAllolinux, gtk, x86 wed=true or false generate attribute in the generated features +#jnlp.configs=${configs} #uncomment to filter the content of the generated jnlp files based on the configuration being built + +#Set to true if you want to sign jars +#signJars=false +#sign.alias= +#sign.keystore= +#sign.storepass= + +#Arguments to send to the zip executable +zipargs= + +#Arguments to send to the tar executable +tarargs= + +#Control the creation of a file containing the version included in each configuration - on by default +#generateVersionsLists=false + +############## BUILD NAMING CONTROL ################ +# The directory into which the build elements are fetched and where +# the build takes place. +buildDirectory=${release.basedir}/pdebuild + +# Type of build. Used in naming the build output. Typically this value is +# one of I, N, M, S, ... +buildType=I + +# ID of the build. Used in naming the build output. +buildId=inspectit + +# Label for the build. Used in naming the build output +buildLabel=${buildType}.${buildId} + +# Timestamp for the build. Used in naming the build output +timestamp=007 + +#The value to be used for the qualifier of a plugin or feature when you want to override the value computed by pde. +#The value will only be applied to plugin or features indicating build.properties, qualifier = context +#forceContextQualifier= + +#Enable / disable the generation of a suffix for the features that use .qualifier. +#The generated suffix is computed according to the content of the feature +#generateFeatureVersionSuffix=true + +############# BASE CONTROL ############# +# Settings for the base Eclipse components and Java class libraries +# against which you are building. +# Base location for anything the build needs to compile against. For example, +# in most RCP app or a plug-in, the baseLocation should be the location of a previously +# installed Eclipse against which the application or plug-in code will be compiled and the RCP delta pack. + +#base= +#baseLocation=${base}/eclipse +#base=${release.basedir}/eclipseBase +base=${release.basedir}/runtime/base +#base=${release.basedir}/runtime/base +baseLocation=${base}/eclipse + +#Os/Ws/Arch/nl of the eclipse specified by baseLocation +#baseos=win32 +#basews=win32 +#basearch=x86 +baseos=linux +basews=gtk +basearch=x86 + +#this property indicates whether you want the set of plug-ins and features to be considered during the build to be limited to the ones reachable from the features / plugins being built +filteredDependencyCheck=false + +#this property indicates whether the resolution should be done in development mode (i.e. ignore multiple bundles with singletons) +resolution.devMode=false + +#pluginPath is a list of locations in which to find plugins and features. This list is separated by the platform file separator (; or :) +#a location is one of: +#- the location of the jar or folder that is the plugin or feature : /path/to/foo.jar or /path/to/foo +#- a directory that contains a /plugins or /features subdirectory +#- the location of a feature.xml, or for 2.1 style plugins, the plugin.xml or fragment.xml +pluginPath=${buildDirectory}/plugins + +skipBase=false +#eclipseURL=http://download.eclipse.org/eclipse/downloads/drops/R-3.4.1-200809111700/download.php?dropFile=eclipse-RCP-3.4.1-linux-gtk.tar.gz +#eclipseBuildId=R-3.4.1-200809111700 +eclipseBaseURL=http://download.eclipse.org/eclipse/downloads/drops/R-3.4.1-200809111700/download.php?dropFile=eclipse-RCP-3.4.1-linux-gtk.tar.gz + + +############# MAP FILE CONTROL ################ +# This section defines CVS tags to use when fetching the map files from the repository. +# If you want to fetch the map file from repository / location, change the getMapFiles target in the customTargets.xml + +skipMaps=true +mapsRepo=:pserver:anonymous@example.com/path/to/repo +mapsRoot=path/to/maps +mapsCheckoutTag=HEAD + +#tagMaps=true +mapsTagTag=v${buildId} + + +############ REPOSITORY CONTROL ############### +# This section defines properties parameterizing the repositories where plugins, fragments +# bundles and features are being obtained from. + +# The tags to use when fetching elements to build. +# By default thebuilder will use whatever is in the maps. +# This value takes the form of a comma separated list of repository identifier (like used in the map files) and the +# overriding value +# For example fetchTag=CVS=HEAD, SVN=v20050101 +# fetchTag=HEAD +skipFetch=true + + +############# JAVA COMPILER OPTIONS ############## +# The location of the Java jars to compile against. Typically the rt.jar for your JDK/JRE +#bootclasspath=${java.home}/lib/rt.jar + +# specific JRE locations to compile against. These values are used to compile bundles specifying a +# Bundle-RequiredExecutionEnvironment. Uncomment and set values for environments that you support +#CDC-1.0/Foundation-1.0= /path/to/rt.jar +#CDC-1.1/Foundation-1.1= +#OSGi/Minimum-1.0= +#OSGi/Minimum-1.1= +#JRE-1.1= +#J2SE-1.2= +#J2SE-1.3= +#J2SE-1.4= +#J2SE-1.5= +#JavaSE-1.6= +#PersonalJava-1.1= +#PersonalJava-1.2= +#CDC-1.0/PersonalBasis-1.0= +#CDC-1.0/PersonalJava-1.0= +#CDC-1.1/PersonalBasis-1.1= +#CDC-1.1/PersonalJava-1.1= + +# Specify the output format of the compiler log when eclipse jdt is used +logExtension=.log + +# Whether or not to include debug info in the output jars +javacDebugInfo=false + +# Whether or not to fail the build if there are compiler errors +javacFailOnError=true + +# Enable or disable verbose mode of the compiler +javacVerbose=true + +# Extra arguments for the compiler. These are specific to the java compiler being used. +compilerArg="-g:lines" + +# Default value for the version of the source code. This value is used when compiling plug-ins that do not set the Bundle-RequiredExecutionEnvironment or set javacSource in build.properties +javacSource=1.7 + +# Default value for the version of the byte code targeted. This value is used when compiling plug-ins that do not set the Bundle-RequiredExecutionEnvironment or set javacTarget in build.properties. +javacTarget=1.7 + +########## P2 METADATA ###################### +generate.p2.metadata = true +p2.metadata.repo=file:${buildDirectory}/repo +p2.artifact.repo=file:${buildDirectory}/repo +p2.flavor=tooling +p2.publish.artifacts=true +############################################# + +########## IVY PROPERTIES ############ +ivy.file = ${basedir}/resources/ivy/ivy.xml +ivy.commonscs.settings.dir=${commonscs.basedir}/resources/ivy +ivy.commonscs.file = ${ivy.commonscs.settings.dir}/ivy.xml + +#marketing.version.target=0.1 + +#dist.root=${buildDirectory}/${buildType}.${buildId} +###################################### + +######################## SOME GENERAL SETTINGS ################################## +src.root=${basedir}/../src +test.root=${basedir}/../test +lib.root=${basedir}/../lib + +commons.plugin=info.novatec.inspectit.commons +commonscs.plugin=info.novatec.inspectit.commonscs + +build.common-targets.file=${commons.basedir}/resources/shared/build/common-targets.xml + +## Settings for TestNG +resources.testng=${basedir}/resources/testng + +build.root=${release.basedir}/build +build.test.classes=${build.root}/classes/test +build.qa.root=${build.root}/QA +build.qa.test=${build.qa.root}/functional_tests +build.qa.test.testdata=${build.qa.test}/testdata +build.qa.test.coveragedata=${build.qa.test}/coveragedata +build.qa.analysis=${build.qa.root}/static_analysis +build.qa.analysis.pmd=${build.qa.analysis}/pmd +build.qa.analysis.findbugs=${build.qa.analysis}/findbugs +build.qa.analysis.checkstyle=${build.qa.analysis}/checkstyle +build.qa.analysis.cpd=${build.qa.analysis}/cpd +build.instrumented.classes=${build.root}/classes/rcpInstrumented +build.rcp.classes=${build.root}/classes/rcp +commons.basedir = ../../Commons + +## Settings for CommonsCS call target +build.commonscs.file=${basedir}/../../CommonsCS/resources/build.xml +ivy.file.commonscs=${basedir}/../../CommonsCS/resources/ivy/ivy.xml + +## Settings for Commons call target +build.commons.file=${basedir}/../../Commons/resources/build.xml +ivy.file.commons=${basedir}/../../Commons/resources/ivy/ivy.xml + +## Settings for Eclipse +ftp.eclipsedir=${ftp.internal.basedir}/rcp_build +eclipse.file=base3.8.2 + +## Settings for JVM installations +jvm.root=${basedir}/jvm +jvm.list=jre7-linux-x64,jre7-linux-x86,jre7-windows-x86,jre7-windows-x64,jre7-macosx-x64 + +########################################################################### +jre.compilation.profile = JavaSE-1.7 +jre.compilation.profile = JavaSE-1.7 + +## Needed because of slf4j / logback ... +allowBinaryCycles=true diff --git a/inspectIT/release/build.xml b/inspectIT/release/build.xml new file mode 100644 index 000000000..c3204b794 --- /dev/null +++ b/inspectIT/release/build.xml @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Press Return key to continue and to overwrite the Eclipse: ${file} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inspectIT/release/resources/ivy/ivy.xml b/inspectIT/release/resources/ivy/ivy.xml new file mode 100644 index 000000000..6f3fa560b --- /dev/null +++ b/inspectIT/release/resources/ivy/ivy.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inspectIT/release/resources/testng/testng.xml b/inspectIT/release/resources/testng/testng.xml new file mode 100644 index 000000000..fc50d9394 --- /dev/null +++ b/inspectIT/release/resources/testng/testng.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/inspectIT/splash.bmp b/inspectIT/splash.bmp new file mode 100644 index 0000000000000000000000000000000000000000..45eb5f9ed967a2e05af32a1e26e95384a9e4fd3a GIT binary patch literal 747896 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

`>-25 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%0A5N;R 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∓dF{dk>z81BKIHBm-@ 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 z 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

A%rMI@hG#I%ZQ1pltKqD7 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 uPn(Z@@V*_s+y@%xJg52H_Gddh+u?Z)Jg + * Currently the property change mechanism of Eclipse RCP is not used in inspectIT. However, it + * might be used in future. + */ + private List propertyChangeListeners = new ArrayList(); + + /** + * Runtime directory of plug-in depending if we are in development or not. + */ + private Path runtimeDir; + + /** + * {@link ILogListener} used for logging. + */ + private ILogListener logListener; + + /** + * This method is called upon plug-in activation. + * + * @param context + * the Context. + * + * @throws Exception + * in case of error. + */ + @Override + public void start(BundleContext context) throws Exception { + plugin = this; + + locateRuntimeDir(); + initLogger(); + + // add log listener once logger is initialized + logListener = new LogListener(); + Platform.addLogListener(logListener); + + super.start(context); + } + + /** + * Locates the runtime directory. It's needed for distinguish between development and runtime. + */ + private void locateRuntimeDir() { + File bundleFile = null; + try { + bundleFile = FileLocator.getBundleFile(getBundle()); + } catch (IOException e) { // NOPMD //NOCHK + } + + if (null != bundleFile && bundleFile.isDirectory()) { + runtimeDir = Paths.get(bundleFile.getAbsolutePath()); + } else { + runtimeDir = Paths.get(""); + } + } + + /** + * Initializes the logger. + */ + private void initLogger() { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + context.reset(); + + InputStream is = null; + + try { + // first check if it's supplied as parameter + String logFileLocation = System.getProperty(LOG_FILE_PROPERTY); + if (null != logFileLocation) { + Path logPath = Paths.get(logFileLocation).toAbsolutePath(); + if (Files.exists(logPath)) { + is = Files.newInputStream(logPath, StandardOpenOption.READ); + } + } + + // then fail to default if none is specified + if (null == is) { + Path logPath = getRuntimeDir().resolve(DEFAULT_LOG_FILE_NAME).toAbsolutePath(); + if (Files.exists(logPath)) { + is = Files.newInputStream(logPath, StandardOpenOption.READ); + } + } + + if (null != is) { + try { + configurator.doConfigure(is); + } catch (JoranException e) { // NOPMD NOCHK StatusPrinter will handle this + } finally { + is.close(); + } + } + } catch (IOException e) { // NOPMD NOCHK StatusPrinter will handle this + } + + StatusPrinter.printInCaseOfErrorsOrWarnings(context); + + // use sysout-over-slf4j to redirect out and err calls to logger + SysOutOverSLF4J.sendSystemOutAndErrToSLF4J(); + } + + /** + * This method is called when the plug-in is stopped. + * + * @param context + * the Context. + * + * @throws Exception + * in case of error. + */ + @Override + public void stop(BundleContext context) throws Exception { + if (null != cmrRepositoryManager) { + cmrRepositoryManager.cancelAllUpdateRepositoriesJobs(); + } + + // remove log listener + Platform.removeLogListener(logListener); + logListener = null; // NOPMD + + super.stop(context); + plugin = null; // NOPMD + } + + /** + * Returns the shared instance. + * + * @return Returns the shared instance. + */ + public static InspectIT getDefault() { + return plugin; + } + + /** + * Registers the {@link IPropertyChangeListener} with the plug-in. Has no effect if the listener + * is already registered. + * + * @param propertyChangeListener + * {@link IPropertyChangeListener} to add. + */ + public void addPropertyChangeListener(IPropertyChangeListener propertyChangeListener) { + if (!propertyChangeListeners.contains(propertyChangeListener)) { + propertyChangeListeners.add(propertyChangeListener); + } + } + + /** + * Unregisters the {@link IPropertyChangeListener} from the plug-in. + * + * @param propertyChangeListener + * {@link IPropertyChangeListener} to remove. + */ + public void removePropertyChangeListener(IPropertyChangeListener propertyChangeListener) { + propertyChangeListeners.remove(propertyChangeListener); + } + + /** + * Delegates the {@link PropertyChangeEvent} to all listeners. + * + * @param event + * Event to delegate. + */ + public void firePropertyChangeEvent(PropertyChangeEvent event) { + for (IPropertyChangeListener listener : propertyChangeListeners) { + listener.propertyChange(event); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void initializeImageRegistry(ImageRegistry reg) { + Field[] allFields = InspectITImages.class.getFields(); + for (Field field : allFields) { + if (field.getName().startsWith("IMG") && String.class.equals(field.getType())) { + if (!field.isAccessible()) { + field.setAccessible(true); + } + try { + String key = (String) field.get(null); + URL url = getBundle().getEntry(key); + reg.put(key, ImageDescriptor.createFromURL(url)); + } catch (Exception e) { + continue; + } + } + } + } + + /** + * Returns an image from the image registry by resolving the passed image key. + *

+ * Images retrieved by this method should not be disposed, because they are shared resources + * in the plugin and will be disposed with the disposal of the display. + * + * @param imageKey + * The key of the image to look for in the registry. + * @return The generated image. + */ + public Image getImage(String imageKey) { + return getImageRegistry().get(imageKey); + } + + /** + * Returns the image descriptor for the given key. The key can be one of the IMG_ definitions in + * {@link InspectITImages}. + *

+ * Every new image created with the given {@link ImageDescriptor} should be disposed by the + * caller. + * + * @param imageKey + * The image key. + * @return The image descriptor for the given image key. + */ + public ImageDescriptor getImageDescriptor(String imageKey) { + return getImageRegistry().getDescriptor(imageKey); + } + + /** + * Returns a service, if one is registered with the bundle context. + * + * @param clazz + * Class of service. + * @param + * Type + * @return Service or null is service is not registered at the moment. + */ + public static E getService(Class clazz) { + ServiceReference reference = getDefault().getBundle().getBundleContext().getServiceReference(clazz); + if (null != reference) { + return getDefault().getBundle().getBundleContext().getService(reference); + } + throw new RuntimeException("Requested service of the class " + clazz.getName() + " is not registered in the bundle."); + } + + /** + * {@inheritDoc} + */ + public ScopedPreferenceStore getPreferenceStore() { + if (null == preferenceStore) { + synchronized (this) { + if (null == preferenceStore) { // NOCHK: DCL works with volatile. + preferenceStore = new ScopedPreferenceStore(ConfigurationScope.INSTANCE, ID); + } + } + } + return preferenceStore; + } + + /** + * @return Returns the CMR repository manager. + */ + public CmrRepositoryManager getCmrRepositoryManager() { + if (null == cmrRepositoryManager) { + synchronized (this) { + if (null == cmrRepositoryManager) { // NOCHK: DCL works with volatile. + cmrRepositoryManager = new CmrRepositoryManager(); + } + } + } + return cmrRepositoryManager; + } + + /** + * + * @return Returns the {@link InspectITStorageManager}. + */ + public InspectITStorageManager getInspectITStorageManager() { + if (null == storageManager) { + synchronized (this) { + if (null == storageManager) { // NOCHK: DCL works with volatile. + storageManager = getService(InspectITStorageManager.class); + storageManager.startUp(); + } + } + } + return storageManager; + } + + /** + * Gets {@link #runtimeDir}. + * + * @return {@link #runtimeDir} + */ + public Path getRuntimeDir() { + return runtimeDir; + } + + /** + * Sets {@link #runtimeDir}. + * + * @param runtimeDir + * New value for {@link #runtimeDir} + */ + public void setRuntimeDir(Path runtimeDir) { + this.runtimeDir = runtimeDir; + } + + /** + * Creates a simple error dialog. + * + * @param message + * The message of the dialog. + * @param throwable + * The exception to display + * @param code + * The code of the error. -1 is a marker that the code has to be added later. + */ + public void createErrorDialog(String message, Throwable throwable, int code) { + IStatus status = new Status(IStatus.ERROR, ID, code, message, throwable); + StatusManager.getManager().handle(status, StatusManager.SHOW | StatusManager.LOG); + } + + /** + * Creates a simple error dialog without exception. + * + * @param message + * The message of the dialog. + * @param code + * The code of the error. -1 is a marker that the code has to be added later. + */ + public void createErrorDialog(String message, int code) { + // Status sets exception to null internally. + IStatus status = new Status(IStatus.ERROR, ID, code, message, null); + StatusManager.getManager().handle(status, StatusManager.SHOW | StatusManager.LOG); + } + + /** + * Creates a simple info dialog. + * + * @param message + * The message of the dialog. + * @param code + * The code of the error. -1 is a marker that the code has to be added later. + */ + public void createInfoDialog(String message, int code) { + MessageDialog.openInformation(null, "Information", message); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/InspectITConstants.java b/inspectIT/src/info/novatec/inspectit/rcp/InspectITConstants.java new file mode 100644 index 000000000..f1aee0680 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/InspectITConstants.java @@ -0,0 +1,36 @@ +package info.novatec.inspectit.rcp; + +/** + * Defines all the constants for the InspectIT UI. + * + * @author Patrice Bouillet + * + */ +public interface InspectITConstants { + + /** + * Path to the folder that holds images. + */ + String ICON_PATH = "/icons/"; + + /** + * Path to the folder that holds images. + */ + String ICON_PATH_SELFMADE = ICON_PATH + "selfmade/"; + + /** + * Path to the folder that holds images. + */ + String ICON_PATH_ECLIPSE = ICON_PATH + "eclipse/"; + + /** + * Path to the folder that holds images. + */ + String ICON_PATH_FUGUE = ICON_PATH + "fugue/"; + + /** + * Path to the folder that holds images. + */ + String WIZBAN_ICON_PATH = ICON_PATH_ECLIPSE + "wizban/"; + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/InspectITImages.java b/inspectIT/src/info/novatec/inspectit/rcp/InspectITImages.java new file mode 100644 index 000000000..805063c9b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/InspectITImages.java @@ -0,0 +1,159 @@ +package info.novatec.inspectit.rcp; + +/** + * Defines all the images for the InspectIT UI. Note that all images are automatically added to the + * registry on the inspectIT start up. + * + * @author Ivan Senic + * + */ +public interface InspectITImages { + + // NOCHKALL: We will not javadoc comment each image. + + // images created by us + String IMG_SERVER_ONLINE = InspectITConstants.ICON_PATH_SELFMADE + "server_online.png"; + String IMG_SERVER_OFFLINE = InspectITConstants.ICON_PATH_SELFMADE + "server_offline.png"; + String IMG_SERVER_ONLINE_SMALL = InspectITConstants.ICON_PATH_SELFMADE + "server_online_16x16.png"; + String IMG_SERVER_OFFLINE_SMALL = InspectITConstants.ICON_PATH_SELFMADE + "server_offline_16x16.png"; + String IMG_SERVER_REFRESH = InspectITConstants.ICON_PATH_SELFMADE + "server_refresh.png"; + String IMG_SERVER_REFRESH_SMALL = InspectITConstants.ICON_PATH_SELFMADE + "server_refresh_16x16.png"; + String IMG_SERVER_ADD = InspectITConstants.ICON_PATH_SELFMADE + "server_add.png"; + String IMG_RETURN = InspectITConstants.ICON_PATH_SELFMADE + "return.png"; + String IMG_PARAMETER = InspectITConstants.ICON_PATH_SELFMADE + "parameter.png"; + String IMG_FIELD = InspectITConstants.ICON_PATH_SELFMADE + "field.png"; + + // images from Eclipse, license EPL 1.0 + String IMG_ACTIVITY = InspectITConstants.ICON_PATH_ECLIPSE + "debugtt_obj.gif"; + String IMG_ADD = InspectITConstants.ICON_PATH_ECLIPSE + "add_obj.gif"; + String IMG_ALERT = InspectITConstants.ICON_PATH_ECLIPSE + "alert_obj.gif"; + String IMG_CALL_HIERARCHY = InspectITConstants.ICON_PATH_ECLIPSE + "call_hierarchy.gif"; + String IMG_CHECKMARK = InspectITConstants.ICON_PATH_ECLIPSE + "complete_status.gif"; + String IMG_CLASS = InspectITConstants.ICON_PATH_ECLIPSE + "class_obj.gif"; + String IMG_CLASS_OVERVIEW = InspectITConstants.ICON_PATH_ECLIPSE + "class_obj.gif"; + String IMG_CLOSE = InspectITConstants.ICON_PATH_ECLIPSE + "remove_co.gif"; + String IMG_COLLAPSE = InspectITConstants.ICON_PATH_ECLIPSE + "collapseall.gif"; + String IMG_COMPILATION_OVERVIEW = InspectITConstants.ICON_PATH_ECLIPSE + "workset.gif"; + String IMG_DELETE = InspectITConstants.ICON_PATH_ECLIPSE + "delete_obj.gif"; + String IMG_DISABLED = InspectITConstants.ICON_PATH_ECLIPSE + "disabled_co.gif"; + String IMG_EXCEPTION_SENSOR = InspectITConstants.ICON_PATH_ECLIPSE + "exceptiontracer.gif"; + String IMG_EXCEPTION_TREE = InspectITConstants.ICON_PATH_ECLIPSE + "exceptiontree.gif"; + String IMG_EXPORT = InspectITConstants.ICON_PATH_ECLIPSE + "export.gif"; + String IMG_FILTER = InspectITConstants.ICON_PATH_ECLIPSE + "filter_ps.gif"; + String IMG_FOLDER = InspectITConstants.ICON_PATH_ECLIPSE + "prj_obj.gif"; + String IMG_FONT = InspectITConstants.ICON_PATH_ECLIPSE + "font.gif"; + String IMG_HELP = InspectITConstants.ICON_PATH_ECLIPSE + "help.gif"; + String IMG_HTTP = InspectITConstants.ICON_PATH_ECLIPSE + "discovery.gif"; + String IMG_HTTP_AGGREGATION_REQUESTMESSAGE = InspectITConstants.ICON_PATH_ECLIPSE + "showcat_co.gif"; + String IMG_HTTP_TAGGED = InspectITConstants.ICON_PATH_ECLIPSE + "gel_sc_obj.gif"; + String IMG_HOME = InspectITConstants.ICON_PATH_ECLIPSE + "home_nav.gif"; + String IMG_IMPORT = InspectITConstants.ICON_PATH_ECLIPSE + "import.gif"; + String IMG_INFORMATION = InspectITConstants.ICON_PATH_ECLIPSE + "info_obj.gif"; + String IMG_ITEM_NA_GREY = InspectITConstants.ICON_PATH_ECLIPSE + "remove_exc.gif"; + String IMG_LIVE_MODE = InspectITConstants.ICON_PATH_ECLIPSE + "start_task.gif"; + String IMG_METHOD_PUBLIC = InspectITConstants.ICON_PATH_ECLIPSE + "methpub_obj.gif"; + String IMG_METHOD_PROTECTED = InspectITConstants.ICON_PATH_ECLIPSE + "methpro_obj.gif"; + String IMG_METHOD_DEFAULT = InspectITConstants.ICON_PATH_ECLIPSE + "methdef_obj.gif"; + String IMG_METHOD_PRIVATE = InspectITConstants.ICON_PATH_ECLIPSE + "methpri_obj.gif"; + String IMG_NEXT = InspectITConstants.ICON_PATH_ECLIPSE + "next_nav.gif"; + String IMG_OVERLAY_UP = InspectITConstants.ICON_PATH_ECLIPSE + "over_co.gif"; + String IMG_PACKAGE = InspectITConstants.ICON_PATH_ECLIPSE + "package_obj.gif"; + String IMG_PREVIOUS = InspectITConstants.ICON_PATH_ECLIPSE + "previous_nav.gif"; + String IMG_PRIORITY = InspectITConstants.ICON_PATH_ECLIPSE + "ihigh_obj.gif"; + String IMG_PROPERTIES = InspectITConstants.ICON_PATH_ECLIPSE + "properties.gif"; + String IMG_PROGRESS_VIEW = InspectITConstants.ICON_PATH_ECLIPSE + "pview.gif"; + String IMG_READ = InspectITConstants.ICON_PATH_ECLIPSE + "read_obj.gif"; + String IMG_REFRESH = InspectITConstants.ICON_PATH_ECLIPSE + "refresh.gif"; + String IMG_REMOVE = InspectITConstants.ICON_PATH_ECLIPSE + "remove_correction.gif"; + String IMG_RESTART = InspectITConstants.ICON_PATH_ECLIPSE + "term_restart.gif"; + String IMG_SEARCH = InspectITConstants.ICON_PATH_ECLIPSE + "insp_sbook.gif"; + String IMG_SHOW_ALL = InspectITConstants.ICON_PATH_ECLIPSE + "all_instances.gif"; + String IMG_STACKTRACE = InspectITConstants.ICON_PATH_ECLIPSE + "stacktrace.gif"; + String IMG_TERMINATE = InspectITConstants.ICON_PATH_ECLIPSE + "terminate_co.gif"; + String IMG_THREADS_OVERVIEW = InspectITConstants.ICON_PATH_ECLIPSE + "debugt_obj.gif"; + String IMG_TIMESTAMP = InspectITConstants.ICON_PATH_ECLIPSE + "dates.gif"; + String IMG_TRASH = InspectITConstants.ICON_PATH_ECLIPSE + "trash.gif"; + String IMG_WARNING = InspectITConstants.ICON_PATH_ECLIPSE + "warning_obj.gif"; + String IMG_WINDOW = InspectITConstants.ICON_PATH_ECLIPSE + "defaultview_misc.gif"; + + // wizard banners, all from Eclipse, license EPL 1.0 + String IMG_WIZBAN_ADD = InspectITConstants.WIZBAN_ICON_PATH + "add_wiz.png"; + String IMG_WIZBAN_DOWNLOAD = InspectITConstants.WIZBAN_ICON_PATH + "download_wiz.png"; + String IMG_WIZBAN_EDIT = InspectITConstants.WIZBAN_ICON_PATH + "edit_wiz.png"; + String IMG_WIZBAN_EXPORT = InspectITConstants.WIZBAN_ICON_PATH + "export_wiz.png"; + String IMG_WIZBAN_IMPORT = InspectITConstants.WIZBAN_ICON_PATH + "import_wiz.png"; + String IMG_WIZBAN_LABEL = InspectITConstants.WIZBAN_ICON_PATH + "label_wiz.png"; + String IMG_WIZBAN_RECORD = InspectITConstants.WIZBAN_ICON_PATH + "record_wiz.png"; + String IMG_WIZBAN_SERVER = InspectITConstants.WIZBAN_ICON_PATH + "server_wiz.png"; + String IMG_WIZBAN_STORAGE = InspectITConstants.WIZBAN_ICON_PATH + "storage_wiz.png"; + String IMG_WIZBAN_UPLOAD = InspectITConstants.WIZBAN_ICON_PATH + "upload_wiz.png"; + + // images originally from Eclipse we modified (resized, added smth, etc), license EPL 1.0 + String IMG_AGENT = InspectITConstants.ICON_PATH_ECLIPSE + "agent.gif"; + String IMG_AGENT_ACTIVE = InspectITConstants.ICON_PATH_ECLIPSE + "agent_active.gif"; + String IMG_AGENT_NOT_ACTIVE = InspectITConstants.ICON_PATH_ECLIPSE + "agent_na.gif"; + String IMG_AGENT_NOT_SENDING = InspectITConstants.ICON_PATH_ECLIPSE + "agent_not_sending.gif"; + String IMG_BUFFER_CLEAR = InspectITConstants.ICON_PATH_ECLIPSE + "buffer_clear.gif"; + String IMG_BUFFER_COPY = InspectITConstants.ICON_PATH_ECLIPSE + "buffer_copy.gif"; + String IMG_BUSINESS = InspectITConstants.ICON_PATH_ECLIPSE + "business.gif"; + String IMG_CALENDAR = InspectITConstants.ICON_PATH_ECLIPSE + "calendar.gif"; + String IMG_CATALOG = InspectITConstants.ICON_PATH_ECLIPSE + "catalog.gif"; + String IMG_CHART_BAR = InspectITConstants.ICON_PATH_ECLIPSE + "graph_bar.gif"; + String IMG_CHART_PIE = InspectITConstants.ICON_PATH_ECLIPSE + "graph_pie.gif"; + String IMG_EDIT = InspectITConstants.ICON_PATH_ECLIPSE + "edit.gif"; + String IMG_FLAG = InspectITConstants.ICON_PATH_ECLIPSE + "flag.gif"; + String IMG_HTTP_URL = InspectITConstants.ICON_PATH_ECLIPSE + "url.gif"; + String IMG_LABEL = InspectITConstants.ICON_PATH_ECLIPSE + "label.gif"; + String IMG_LABEL_ADD = InspectITConstants.ICON_PATH_ECLIPSE + "label_add.gif"; + String IMG_LABEL_DELETE = InspectITConstants.ICON_PATH_ECLIPSE + "label_delete.gif"; + String IMG_LOCATE_IN_HIERARCHY = InspectITConstants.ICON_PATH_ECLIPSE + "locate_in_hierarchy.gif"; + String IMG_METHOD = InspectITConstants.ICON_PATH_ECLIPSE + "method.gif"; + String IMG_MESSAGE = InspectITConstants.ICON_PATH_ECLIPSE + "message.gif"; + String IMG_NUMBER = InspectITConstants.ICON_PATH_ECLIPSE + "number.gif"; + String IMG_OVERLAY_ERROR = InspectITConstants.ICON_PATH_ECLIPSE + "overlay_error.gif"; + String IMG_OVERLAY_PRIORITY = InspectITConstants.ICON_PATH_ECLIPSE + "overlay_priority.gif"; + String IMG_PREFERENCES = InspectITConstants.ICON_PATH_ECLIPSE + "preferences.gif"; + String IMG_RECORD = InspectITConstants.ICON_PATH_ECLIPSE + "record.gif"; + String IMG_RECORD_GRAY = InspectITConstants.ICON_PATH_ECLIPSE + "record_gray.gif"; + String IMG_RECORD_SCHEDULED = InspectITConstants.ICON_PATH_ECLIPSE + "record_schedule.gif"; + String IMG_RECORD_STOP = InspectITConstants.ICON_PATH_ECLIPSE + "record_term.gif"; + String IMG_OPTIONS = InspectITConstants.ICON_PATH_ECLIPSE + "options.gif"; + String IMG_SERVER_INSTACE = InspectITConstants.ICON_PATH_ECLIPSE + "server_instance.gif"; + String IMG_STORAGE = InspectITConstants.ICON_PATH_ECLIPSE + "storage.gif"; + String IMG_STORAGE_AVAILABLE = InspectITConstants.ICON_PATH_ECLIPSE + "storage_available.gif"; + String IMG_STORAGE_CLOSED = InspectITConstants.ICON_PATH_ECLIPSE + "storage_readable.gif"; + String IMG_STORAGE_DOWNLOADED = InspectITConstants.ICON_PATH_ECLIPSE + "storage_download.gif"; + String IMG_STORAGE_FINALIZE = InspectITConstants.ICON_PATH_ECLIPSE + "storage_finalaze.gif"; + String IMG_STORAGE_NOT_AVAILABLE = InspectITConstants.ICON_PATH_ECLIPSE + "storage_na.gif"; + String IMG_STORAGE_OPENED = InspectITConstants.ICON_PATH_ECLIPSE + "storage_writable.gif"; + String IMG_STORAGE_OVERLAY = InspectITConstants.ICON_PATH_ECLIPSE + "storage_overlay.gif"; + String IMG_STORAGE_RECORDING = InspectITConstants.ICON_PATH_ECLIPSE + "storage_recording.gif"; + String IMG_STORAGE_UPLOAD = InspectITConstants.ICON_PATH_ECLIPSE + "storge_upload.gif"; + String IMG_TIME = InspectITConstants.ICON_PATH_ECLIPSE + "time.gif"; + String IMG_TIME_DELTA = InspectITConstants.ICON_PATH_ECLIPSE + "time_delta.gif"; + String IMG_TIMEFRAME = InspectITConstants.ICON_PATH_ECLIPSE + "timeframe.gif"; + String IMG_TIMER = InspectITConstants.ICON_PATH_ECLIPSE + "method_time.gif"; + String IMG_TOOL = InspectITConstants.ICON_PATH_ECLIPSE + "build.gif"; + String IMG_TRANSFORM = InspectITConstants.ICON_PATH_ECLIPSE + "transform.gif"; + String IMG_USER = InspectITConstants.ICON_PATH_ECLIPSE + "user.gif"; + + // Fugue set - license Creative Commons v3.0 + String IMG_CPU_OVERVIEW = InspectITConstants.ICON_PATH_FUGUE + "processor.png"; + String IMG_DATABASE = InspectITConstants.ICON_PATH_FUGUE + "database-sql.png"; + String IMG_INSTRUMENTATION_BROWSER = InspectITConstants.ICON_PATH_FUGUE + "blue-document-tree.png"; + String IMG_INSTRUMENTATION_BROWSER_INACTIVE = InspectITConstants.ICON_PATH_FUGUE + "document-tree.png"; + String IMG_INVOCATION = InspectITConstants.ICON_PATH_FUGUE + "arrow-switch.png"; + String IMG_MEMORY_OVERVIEW = InspectITConstants.ICON_PATH_FUGUE + "memory.png"; + String IMG_SYSTEM_OVERVIEW = InspectITConstants.ICON_PATH_FUGUE + "system-monitor.png"; + String IMG_VM_SUMMARY = InspectITConstants.ICON_PATH_FUGUE + "resource-monitor.png"; + + // labels just pointing to existing ones + String IMG_ASSIGNEE_LABEL_ICON = IMG_USER; + String IMG_DATE_LABEL_ICON = IMG_CALENDAR; + String IMG_MOUNTEDBY_LABEL_ICON = IMG_READ; + String IMG_RATING_LABEL_ICON = IMG_PRIORITY; + String IMG_STATUS_LABEL_ICON = IMG_ALERT; + String IMG_USECASE_LABEL_ICON = IMG_BUSINESS; + String IMG_USER_LABEL_ICON = IMG_DISABLED; + +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/Perspective.java b/inspectIT/src/info/novatec/inspectit/rcp/Perspective.java new file mode 100644 index 000000000..faf8320e1 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/Perspective.java @@ -0,0 +1,40 @@ +package info.novatec.inspectit.rcp; + +import info.novatec.inspectit.rcp.view.impl.DataExplorerView; +import info.novatec.inspectit.rcp.view.impl.RepositoryManagerView; +import info.novatec.inspectit.rcp.view.impl.StorageManagerView; + +import org.eclipse.ui.IFolderLayout; +import org.eclipse.ui.IPageLayout; +import org.eclipse.ui.IPerspectiveFactory; + +/** + * The default perspective and layout of the InspectIT UI. + * + * @author Patrice Bouillet + * + */ +public class Perspective implements IPerspectiveFactory { + + /** + * {@inheritDoc} + */ + public void createInitialLayout(IPageLayout layout) { + layout.setEditorAreaVisible(true); + layout.setFixed(false); + float ratio = 0.4f; + String editorArea = layout.getEditorArea(); + + IFolderLayout topLeft = layout.createFolder("topLeft", IPageLayout.LEFT, ratio, editorArea); + topLeft.addView(RepositoryManagerView.VIEW_ID); + topLeft.addView(StorageManagerView.VIEW_ID); + topLeft.addView(DataExplorerView.VIEW_ID); + + layout.getViewLayout(RepositoryManagerView.VIEW_ID).setCloseable(true); + layout.getViewLayout(RepositoryManagerView.VIEW_ID).setMoveable(true); + layout.getViewLayout(StorageManagerView.VIEW_ID).setCloseable(true); + layout.getViewLayout(StorageManagerView.VIEW_ID).setMoveable(true); + layout.getViewLayout(DataExplorerView.VIEW_ID).setCloseable(true); + layout.getViewLayout(DataExplorerView.VIEW_ID).setMoveable(true); + } +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/action/MenuAction.java b/inspectIT/src/info/novatec/inspectit/rcp/action/MenuAction.java new file mode 100644 index 000000000..6bfd4738f --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/action/MenuAction.java @@ -0,0 +1,71 @@ +package info.novatec.inspectit.rcp.action; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IContributionItem; +import org.eclipse.jface.action.IMenuCreator; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Menu; + +/** + * Action to add a menu to the preference view. + * + * @author Patrice Bouillet + * + */ +public final class MenuAction extends Action implements IMenuCreator { + + /** + * The menu manager. + */ + private final MenuManager menuManager; + + /** + * Creates a new menu. + */ + public MenuAction() { + super("", Action.AS_DROP_DOWN_MENU); + menuManager = new MenuManager(); + setMenuCreator(this); + } + + /** + * Adds a contribution item to this manager, like a sub-menu ... + * + * @param contributionItem + * THe contribution item to add. + */ + public void addContributionItem(IContributionItem contributionItem) { + menuManager.add(contributionItem); + } + + /** + * @see MenuManager#getSize() + * @return the number of contributions in this manager. + */ + public int getSize() { + return menuManager.getSize(); + } + + /** + * {@inheritDoc} + */ + public Menu getMenu(Control parent) { + return menuManager.createContextMenu(parent); + } + + /** + * {@inheritDoc} + */ + public Menu getMenu(Menu parent) { + return null; + } + + /** + * {@inheritDoc} + */ + public void dispose() { + menuManager.dispose(); + } + +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/composite/BreadcrumbTitleComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/composite/BreadcrumbTitleComposite.java new file mode 100644 index 000000000..fefed84ba --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/composite/BreadcrumbTitleComposite.java @@ -0,0 +1,350 @@ +package info.novatec.inspectit.rcp.composite; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.formatter.ImageFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.repository.CmrRepositoryChangeListener; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; +import info.novatec.inspectit.rcp.repository.StorageRepositoryDefinition; +import info.novatec.inspectit.rcp.storage.listener.StorageChangeListener; +import info.novatec.inspectit.rcp.util.AccessibleArrowImage; +import info.novatec.inspectit.storage.IStorageData; + +import java.util.Objects; + +import org.eclipse.jface.action.ToolBarManager; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CLabel; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.ToolBar; + +/** + * A composite to be the head client of the form that + * {@link info.novatec.inspectit.rcp.editor.root.FormRootEditor} is made of. + * + * @author Ivan Senic + * + */ +public class BreadcrumbTitleComposite extends Composite implements CmrRepositoryChangeListener, StorageChangeListener { + + /** + * Maximum text length for the label content. + */ + private static final int MAX_TEXT_LENGTH = 30; + + /** + * Arrow to be displayed between the breadcrumbs. + */ + private final Image arrow; + + /** + * {@link ToolBarManager} of the composite. + */ + private ToolBarManager toolBarManager; + + /** + * Displayed repository definition. + */ + private RepositoryDefinition repositoryDefinition; + + /** + * Label for repository name. + */ + private CLabel repositoryLabel; + + /** + * Label for agent name. + */ + private CLabel agentLabel; + + /** + * Label for group description. + */ + private CLabel groupLabel; + + /** + * Label for view description. + */ + private CLabel viewLabel; + + /** + * Default constructor. + * + * @param parent + * Parent composite. + * @param style + * Style. + * @see Composite#Composite(Composite, int) + */ + public BreadcrumbTitleComposite(Composite parent, int style) { + super(parent, style); + arrow = new AccessibleArrowImage(true).createImage(); + init(); + } + + /** + * Initializes the widget. + */ + private void init() { + // define layout + GridLayout gridLayout = new GridLayout(2, false); + gridLayout.marginHeight = 0; + gridLayout.marginWidth = 0; + setLayout(gridLayout); + + Composite breadcrumbComposite = new Composite(this, SWT.NONE); + breadcrumbComposite.setLayout(new GridLayout(7, false)); + breadcrumbComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + + repositoryLabel = new CLabel(breadcrumbComposite, SWT.NONE); + repositoryLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + + new Label(breadcrumbComposite, SWT.NONE).setImage(arrow); + + agentLabel = new CLabel(breadcrumbComposite, SWT.NONE); + agentLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + + new Label(breadcrumbComposite, SWT.NONE).setImage(arrow); + + groupLabel = new CLabel(breadcrumbComposite, SWT.NONE); + groupLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + + new Label(breadcrumbComposite, SWT.NONE).setImage(arrow); + + viewLabel = new CLabel(breadcrumbComposite, SWT.NONE); + viewLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + // create tool-bar and its manager + ToolBar toolbar = new ToolBar(this, SWT.FLAT); + toolbar.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false)); + toolBarManager = new ToolBarManager(toolbar); + } + + /** + * Gets {@link #toolBarManager}. + * + * @return {@link #toolBarManager} + */ + public ToolBarManager getToolBarManager() { + return toolBarManager; + } + + /** + * Sets {@link #repositoryDefinition}. + * + * @param repositoryDefinition + * New value for {@link #repositoryDefinition} + */ + public void setRepositoryDefinition(RepositoryDefinition repositoryDefinition) { + this.repositoryDefinition = repositoryDefinition; + repositoryLabel.setText(TextFormatter.crop(repositoryDefinition.getName(), MAX_TEXT_LENGTH)); + if (repositoryDefinition instanceof CmrRepositoryDefinition) { + repositoryLabel.setImage(ImageFormatter.getCmrRepositoryImage((CmrRepositoryDefinition) repositoryDefinition, true)); + InspectIT.getDefault().getCmrRepositoryManager().addCmrRepositoryChangeListener(this); + } else if (repositoryDefinition instanceof StorageRepositoryDefinition) { + repositoryLabel.setImage(ImageFormatter.getStorageRepositoryImage((StorageRepositoryDefinition) repositoryDefinition)); + InspectIT.getDefault().getInspectITStorageManager().addStorageChangeListener(this); + } + } + + /** + * Sets the agent name. + * + * @param agentName + * Agent name. + * @param agentImg + * Image to go next to the agent name. If null is passed no changed to + * the current image will be done. + */ + public void setAgent(String agentName, Image agentImg) { + agentLabel.setText(TextFormatter.crop(agentName, MAX_TEXT_LENGTH)); + agentLabel.setToolTipText(agentName); + if (null != agentImg) { + agentLabel.setImage(agentImg); + } + } + + /** + * Sets the title text and image. + * + * @param group + * Group description. + * @param groupdImg + * Image to go next to the title. If null is passed no changed to the + * current image will be done. + */ + public void setGroup(String group, Image groupdImg) { + groupLabel.setText(TextFormatter.crop(group, MAX_TEXT_LENGTH)); + groupLabel.setToolTipText(group); + if (null != groupdImg) { + groupLabel.setImage(groupdImg); + } + layoutInternal(); + } + + /** + * Sets the description. + * + * @param view + * View description. + * @param viewImg + * Image to go next to the view description. If null is passed no + * changed to the current image will be done. + */ + public void setView(String view, Image viewImg) { + viewLabel.setText(view); + viewLabel.setToolTipText(view); + if (null != viewImg) { + viewLabel.setImage(viewImg); + } + layoutInternal(); + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryOnlineStatusUpdated(CmrRepositoryDefinition cmrRepositoryDefinition, OnlineStatus oldStatus, OnlineStatus newStatus) { + if (newStatus != OnlineStatus.CHECKING && Objects.equals(repositoryDefinition, cmrRepositoryDefinition)) { + getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + repositoryLabel.setImage(ImageFormatter.getCmrRepositoryImage((CmrRepositoryDefinition) repositoryDefinition, true)); + } + }); + + } + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryDataUpdated(CmrRepositoryDefinition cmrRepositoryDefinition) { + if (Objects.equals(repositoryDefinition, cmrRepositoryDefinition)) { + getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + repositoryLabel.setText(repositoryDefinition.getName()); + layoutInternal(); + } + }); + } + + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryAgentDeleted(CmrRepositoryDefinition cmrRepositoryDefinition, PlatformIdent agent) { + } + + /** + * Layouts the widget so that the text is properly displayed on the composite. + */ + private void layoutInternal() { + layout(true, true); + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryAdded(CmrRepositoryDefinition cmrRepositoryDefinition) { + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryRemoved(CmrRepositoryDefinition cmrRepositoryDefinition) { + } + + /** + * {@inheritDoc} + */ + @Override + public void storageDataUpdated(IStorageData storageData) { + updateStorageDetailsIfDisplayed(storageData); + } + + /** + * {@inheritDoc} + */ + @Override + public void storageRemotelyDeleted(IStorageData storageData) { + updateStorageDetailsIfDisplayed(storageData); + } + + /** + * {@inheritDoc} + */ + @Override + public void storageLocallyDeleted(IStorageData storageData) { + updateStorageDetailsIfDisplayed(storageData); + } + + /** + * Updates storage name and icon if given storageData is displayed currently on the breadcrumb. + * + * @param storageData + * {@link IStorageData} + */ + private void updateStorageDetailsIfDisplayed(IStorageData storageData) { + if (repositoryDefinition instanceof StorageRepositoryDefinition) { + final StorageRepositoryDefinition storageRepositoryDefinition = (StorageRepositoryDefinition) repositoryDefinition; + if (Objects.equals(storageRepositoryDefinition.getLocalStorageData(), storageData)) { + getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + repositoryLabel.setText(repositoryDefinition.getName()); + repositoryLabel.setImage(ImageFormatter.getStorageRepositoryImage(storageRepositoryDefinition)); + layoutInternal(); + } + }); + } + } + } + + /** + * Returns textual representation of the displayed data for the copy purposes. + * + * @return Returns textual representation of the displayed data for the copy purposes. + */ + public String getCopyString() { + StringBuilder sb = new StringBuilder(); + + sb.append(repositoryLabel.getText()); + sb.append(" > "); + sb.append(agentLabel.getText()); + if (null != groupLabel.getText()) { + sb.append(" > "); + sb.append(groupLabel.getText()); + } + if (null != viewLabel.getText()) { + sb.append(" > "); + sb.append(viewLabel.getText()); + } + return sb.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + arrow.dispose(); + InspectIT.getDefault().getCmrRepositoryManager().removeCmrRepositoryChangeListener(this); + InspectIT.getDefault().getInspectITStorageManager().removeStorageChangeListener(this); + super.dispose(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/composite/StorageInfoComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/composite/StorageInfoComposite.java new file mode 100644 index 000000000..ba5a709d0 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/composite/StorageInfoComposite.java @@ -0,0 +1,252 @@ +package info.novatec.inspectit.rcp.composite; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.storage.IStorageData; +import info.novatec.inspectit.storage.LocalStorageData; +import info.novatec.inspectit.storage.StorageData; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.jface.dialogs.PopupDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.forms.events.HyperlinkAdapter; +import org.eclipse.ui.forms.events.HyperlinkEvent; +import org.eclipse.ui.forms.widgets.FormText; + +/** + * Composite that show the storage info. + * + * @author Ivan Senic + * + */ +public class StorageInfoComposite extends Composite { + + /** + * Not available string. + */ + private static final String NOT_AVAILABLE = "N/A"; + + /** + * Max storage description that will be displayed. + */ + private static final int MAX_DESCRIPTION_LENGTH = 100; + + /** + * Data to display. + */ + private IStorageData storageData; + + /** name of the storage. */ + private Label name; + /** description of the storage. */ + private FormText description; + /** Label holding the size of the storage. */ + private Label size; + /** Label describing if the storage was already downloaded. */ + private Label downloaded; + /** Label for CMR version. */ + private Label cmrVersion; + + /** + * If there should be information if storage is downloaded or not. + */ + private boolean showDataDownloaded; + + /** + * Default constructor. + * + * @param parent + * a widget which will be the parent of the new instance (cannot be null) + * @param style + * the style of widget to construct + * @param showDataDownloaded + * If there should be information if storage is downloaded or not. + * @see Composite#Composite(Composite, int) + */ + public StorageInfoComposite(Composite parent, int style, boolean showDataDownloaded) { + super(parent, style); + this.showDataDownloaded = showDataDownloaded; + init(); + } + + /** + * Secondary constructor. Displays the information from the storage data. + * + * @param parent + * a widget which will be the parent of the new instance (cannot be null) + * @param style + * the style of widget to construct + * @param showDataDownloaded + * If there should be information if storage is downloaded or not. + * @param storageData + * Data to display information for. + */ + public StorageInfoComposite(Composite parent, int style, boolean showDataDownloaded, IStorageData storageData) { + this(parent, style, showDataDownloaded); + displayStorageData(storageData); + } + + /** + * Initializes the widget. + */ + private void init() { + // define layout + GridLayout gridLayout = new GridLayout(1, false); + gridLayout.marginHeight = 0; + gridLayout.marginWidth = 0; + setLayout(gridLayout); + + Group group = new Group(this, SWT.NONE); + group.setText("Storage Info"); + GridLayout gl = new GridLayout(2, false); + gl.marginHeight = 10; + gl.marginWidth = 10; + group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + group.setLayout(gl); + + Label label = new Label(group, SWT.NONE); + label.setText("Name:"); + label.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + name = new Label(group, SWT.WRAP); + name.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + + label = new Label(group, SWT.NONE); + label.setText("Description:"); + label.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + description = new FormText(group, SWT.NO_FOCUS | SWT.WRAP); + GridData gd = new GridData(SWT.FILL, SWT.TOP, true, false); + gd.widthHint = 400; + description.setLayoutData(gd); + description.addHyperlinkListener(new HyperlinkAdapter() { + @Override + public void linkActivated(HyperlinkEvent e) { + showStorageDescriptionBox(); + } + }); + + label = new Label(group, SWT.NONE); + label.setText("Size on disk:"); + label.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + size = new Label(group, SWT.WRAP); + size.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + + label = new Label(group, SWT.NONE); + label.setText("CMR version:"); + label.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + cmrVersion = new Label(group, SWT.WRAP); + cmrVersion.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + + if (showDataDownloaded) { + label = new Label(group, SWT.NONE); + label.setText("Data downloaded:"); + label.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + downloaded = new Label(group, SWT.WRAP); + downloaded.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + } + } + + /** + * Displays the storage data. + * + * @param storageData + * Data to display information for. + */ + public final void displayStorageData(IStorageData storageData) { + this.storageData = storageData; + if (null != storageData) { + name.setText(storageData.getName()); + if (null != storageData.getDescription()) { + if (storageData.getDescription().length() > MAX_DESCRIPTION_LENGTH) { + description.setText("

", true, false); + } else { + description.setText(storageData.getDescription(), false, false); + } + } else { + description.setText("", false, false); + } + size.setText(NumberFormatter.humanReadableByteCount(storageData.getDiskSize())); + if (StringUtils.isNotEmpty(storageData.getCmrVersion())) { + cmrVersion.setText(storageData.getCmrVersion()); + } + if (showDataDownloaded) { + LocalStorageData localStorageData = null; + if (storageData instanceof LocalStorageData) { + localStorageData = (LocalStorageData) storageData; + } else if (storageData instanceof StorageData) { + localStorageData = InspectIT.getDefault().getInspectITStorageManager().getLocalDataForStorage((StorageData) storageData); + } + boolean notDownloaded = (null == localStorageData || !localStorageData.isFullyDownloaded()); + + if (notDownloaded) { + downloaded.setText("No"); + } else { + downloaded.setText("Yes"); + } + } + } else { + showDataUnavailable(); + } + this.layout(true, true); + } + + /** + * Updates the composite to display the not available info. + */ + public final void showDataUnavailable() { + name.setText(NOT_AVAILABLE); + description.setText(NOT_AVAILABLE, false, false); + size.setText(NOT_AVAILABLE); + cmrVersion.setText(NOT_AVAILABLE); + if (showDataDownloaded) { + downloaded.setText(NOT_AVAILABLE); + } + } + + /** + * Shows storage description box. + */ + private void showStorageDescriptionBox() { + int shellStyle = SWT.CLOSE | SWT.TITLE | SWT.BORDER | SWT.APPLICATION_MODAL | SWT.RESIZE; + PopupDialog popupDialog = new PopupDialog(getShell(), shellStyle, true, false, false, false, false, "Storage description", "Storage description") { + private static final int CURSOR_SIZE = 15; + + @Override + protected Control createDialogArea(Composite parent) { + Composite composite = (Composite) super.createDialogArea(parent); + Text text = new Text(parent, SWT.MULTI | SWT.READ_ONLY | SWT.WRAP | SWT.H_SCROLL | SWT.V_SCROLL); + GridData gd = new GridData(GridData.BEGINNING | GridData.FILL_BOTH); + gd.horizontalIndent = 3; + gd.verticalIndent = 3; + text.setLayoutData(gd); + text.setText(storageData.getDescription()); + return composite; + } + + @Override + protected Point getInitialLocation(Point initialSize) { + // show popup relative to cursor + Display display = getShell().getDisplay(); + Point location = display.getCursorLocation(); + location.x += CURSOR_SIZE; + location.y += CURSOR_SIZE; + return location; + } + + @Override + protected Point getInitialSize() { + return new Point(400, 200); + } + }; + popupDialog.open(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/details/DetailsCellContent.java b/inspectIT/src/info/novatec/inspectit/rcp/details/DetailsCellContent.java new file mode 100644 index 000000000..4d9bf6959 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/details/DetailsCellContent.java @@ -0,0 +1,218 @@ +package info.novatec.inspectit.rcp.details; + +import org.eclipse.swt.graphics.Image; + +/** + * Defines content of one cell in the details table. + * + * @author Ivan Senic + * + */ +public class DetailsCellContent { + + /** + * Text to display. + */ + private String text; + + /** + * Image to display. + */ + private Image image; + + /** + * Tool-tip on the image. + */ + private String imageToolTip; + + /** + * Denotes if the content might be a long text. If this option is used no image can be displayed + * alongside the text. In addition, {@link #grab} will be ignored if {@link #longText} is set to + * true. + */ + private boolean longText; + + /** + * How much columns should cell span. + */ + private int colspan = 1; + + /** + * If cell should grab horizontal space. + */ + private boolean grab = true; + + /** + * No-arg constructor. + */ + public DetailsCellContent() { + } + + /** + * Constructor defining only text. + * + * @param text + * Text to display. + */ + public DetailsCellContent(String text) { + this.text = text; + } + + /** + * Constructor defining only text and if long text options should be used. + * + * @param text + * Text to display. + * @param longText + * If text is expected to be a long text, thus different display options will be + * used. + */ + public DetailsCellContent(String text, boolean longText) { + this.text = text; + this.longText = longText; + } + + /** + * Constructor defining only image. + * + * @param image + * Image to display. + * @param imageToolTip + * Tool-tip on the image. + */ + public DetailsCellContent(Image image, String imageToolTip) { + this.image = image; + this.imageToolTip = imageToolTip; + } + + /** + * Constructor defining text and image. + * + * @param text + * Text to display. + * @param image + * Image to display. + * @param imageToolTip + * Tool-tip on the image. + */ + public DetailsCellContent(String text, Image image, String imageToolTip) { + this.text = text; + this.image = image; + this.imageToolTip = imageToolTip; + } + + /** + * Gets {@link #text}. + * + * @return {@link #text} + */ + public String getText() { + return text; + } + + /** + * Sets {@link #text}. + * + * @param text + * New value for {@link #text} + */ + public void setText(String text) { + this.text = text; + } + + /** + * Gets {@link #image}. + * + * @return {@link #image} + */ + public Image getImage() { + return image; + } + + /** + * Sets {@link #image}. + * + * @param image + * New value for {@link #image} + */ + public void setImage(Image image) { + this.image = image; + } + + /** + * Gets {@link #imageToolTip}. + * + * @return {@link #imageToolTip} + */ + public String getImageToolTip() { + return imageToolTip; + } + + /** + * Sets {@link #imageToolTip}. + * + * @param imageToolTip + * New value for {@link #imageToolTip} + */ + public void setImageToolTip(String imageToolTip) { + this.imageToolTip = imageToolTip; + } + + /** + * Gets {@link #longText}. + * + * @return {@link #longText} + */ + boolean isLongText() { + return longText; + } + + /** + * Sets {@link #longText}. + * + * @param longText + * New value for {@link #longText} + */ + void setLongText(boolean longText) { + this.longText = longText; + } + + /** + * Gets {@link #colspan}. + * + * @return {@link #colspan} + */ + public int getColspan() { + return colspan; + } + + /** + * Sets {@link #colspan}. + * + * @param colspan + * New value for {@link #colspan} + */ + public void setColspan(int colspan) { + this.colspan = colspan; + } + + /** + * Gets {@link #grab}. + * + * @return {@link #grab} + */ + public boolean isGrab() { + return grab; + } + + /** + * Sets {@link #grab}. + * + * @param grab + * New value for {@link #grab} + */ + public void setGrab(boolean grab) { + this.grab = grab; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/details/DetailsGenerationFactory.java b/inspectIT/src/info/novatec/inspectit/rcp/details/DetailsGenerationFactory.java new file mode 100644 index 000000000..997a24a75 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/details/DetailsGenerationFactory.java @@ -0,0 +1,79 @@ +package info.novatec.inspectit.rcp.details; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.rcp.details.generator.IDetailsGenerator; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * Factory for generation of the details composites. This class is initialized by Spring. + * + * @author Ivan Senic + * + */ +public final class DetailsGenerationFactory { + + /** + * All the generators. Initialized by Spring. + */ + private List generators; + + /** + * Creates list of detail composites displaying different types of the information for the given + * default data. + * + * @param defaultData + * Data to generate details for. + * @param repositoryDefinition + * {@link RepositoryDefinition} + * @param parent + * Parent composite + * @param toolkit + * {@link FormToolkit} + * @return List of generated composites. + */ + public List createDetailComposites(DefaultData defaultData, RepositoryDefinition repositoryDefinition, Composite parent, FormToolkit toolkit) { + // this is a work-around to include more info about the contained invocation sequence data + DefaultData secondary = null; + if (defaultData instanceof InvocationSequenceData) { + InvocationSequenceData invocationSequenceData = (InvocationSequenceData) defaultData; + if (null != invocationSequenceData.getTimerData()) { + secondary = invocationSequenceData.getTimerData(); + } + if (null != invocationSequenceData.getSqlStatementData()) { + secondary = invocationSequenceData.getSqlStatementData(); + } + if (CollectionUtils.isNotEmpty(invocationSequenceData.getExceptionSensorDataObjects())) { + secondary = invocationSequenceData.getExceptionSensorDataObjects().get(0); + } + } + + List result = new ArrayList<>(); + for (IDetailsGenerator generator : generators) { + if (generator.canGenerateFor(defaultData)) { + result.add(generator.generate(defaultData, repositoryDefinition, parent, toolkit)); + } else if (null != secondary && generator.canGenerateFor(secondary)) { + result.add(generator.generate(secondary, repositoryDefinition, parent, toolkit)); + } + } + return result; + } + + /** + * Sets {@link #generators}. + * + * @param generators + * New value for {@link #generators} + */ + public void setGenerators(List generators) { + this.generators = generators; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/details/DetailsTable.java b/inspectIT/src/info/novatec/inspectit/rcp/details/DetailsTable.java new file mode 100644 index 000000000..04e1eec47 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/details/DetailsTable.java @@ -0,0 +1,322 @@ +package info.novatec.inspectit.rcp.details; + +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.forms.IFormColors; +import org.eclipse.ui.forms.SectionPart; +import org.eclipse.ui.forms.widgets.FormText; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Section; +import org.eclipse.ui.forms.widgets.TableWrapData; +import org.eclipse.ui.forms.widgets.TableWrapLayout; + +/** + * Our own HTML-like table to use for displaying one part of the details for an element. + * + * @see #DetailsTable(Composite, FormToolkit, String, int) + * @see #addContentRow(String, Image, DetailsCellContent[]) + * + * @author Ivan Senic + * + */ +public class DetailsTable extends SectionPart { + + /** + * Width of the row title. + */ + private static final int ROW_TITLE_WIDTH_HINT = 150; + + /** + * Maximum suggested width for the text box displaying large text. + */ + private static final int CONTENT_CONTROL_MAX_WIDTH = 600; + + /** + * Maximum suggested height for the text box displaying large text. + */ + private static final int CONTENT_CONTROL_MAX_HEIGHT = 100; + + /** + * {@link FormText} to create elements. + */ + private FormToolkit toolkit; + + /** + * Number of columns in the information area. Note that this number should not include the + * column used for row title. + */ + private int columns; + + /** + * Content composite. + */ + private Composite contentComposite; + + /** + * Copy string builder. + */ + private StringBuilder copyStringBuilder = new StringBuilder(); // NOPMD + + /** + * Default constructor. + * + * @param parent + * Composite to create on. + * @param toolkit + * {@link FormToolkit} + * @param heading + * Name of the table that will be displayed as header. + * @param columns + * Number of columns in the information area. Note that this number should not + * include the column used for row title. + */ + public DetailsTable(Composite parent, FormToolkit toolkit, String heading, int columns) { + super(parent, toolkit, Section.TITLE_BAR | Section.TWISTIE | Section.EXPANDED); + this.columns = columns; + this.toolkit = toolkit; + + getSection().setLayout(new TableWrapLayout()); + + createHeading(heading); + + copyStringBuilder.append(heading); + copyStringBuilder.append('\n'); + + initTable(); + } + + /** + * Adds one row to the table.. + * + * @param title + * Row title. + * @param image + * Row image displayed next to the row title. Can be null for no image. + * @param cellContents + * Array of {@link DetailsCellContent} objects representing the data in the row. Note + * that the provided array should have same length as the {@link #columns} value + * provided in the constructor. + */ + public void addContentRow(String title, Image image, DetailsCellContent[] cellContents) { + createRowHeading(title, image); + if (null != title) { + copyStringBuilder.append(title); + } + copyStringBuilder.append('\t'); + + // add each column + for (int i = 0; i < columns; i++) { + if (i < cellContents.length) { + DetailsCellContent cellContent = cellContents[i]; + TableWrapData tableWrapData = getLayoutData(cellContent); + if (cellContent.isLongText()) { + Text text = toolkit.createText(contentComposite, cellContent.getText(), SWT.READ_ONLY | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + tableWrapData.grabHorizontal = true; + tableWrapData.maxWidth = CONTENT_CONTROL_MAX_WIDTH; + tableWrapData.maxHeight = CONTENT_CONTROL_MAX_HEIGHT; + text.setLayoutData(tableWrapData); + } else { + FormText formText = toolkit.createFormText(contentComposite, false); + fillFormText(formText, cellContent); + formText.setLayoutData(tableWrapData); + } + copyStringBuilder.append(cellContent.getText()); + } else { + toolkit.createLabel(contentComposite, ""); + } + + if (i < columns - 1) { + copyStringBuilder.append('\t'); + } + } + copyStringBuilder.append('\n'); + } + + /** + * Adds one row to the table by inserting SWT table as content. + * + * @param title + * Row title. + * @param image + * Row image displayed next to the row title. Can be null for no image. + * @param cols + * Number of columns in the SWT table + * @param headers + * Titles for the columns. + * @param rows + * Rows data. Each string array represents one row in the table. + */ + public void addContentTable(String title, Image image, int cols, String[] headers, List rows) { + createRowHeading(title, image); + if (null != title) { + copyStringBuilder.append(title); + } + + Table table = toolkit.createTable(contentComposite, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.V_SCROLL | SWT.VIRTUAL); + table.setHeaderVisible(true); + + TableWrapData tableWrapData = new TableWrapData(); + tableWrapData.colspan = this.columns; + tableWrapData.maxWidth = CONTENT_CONTROL_MAX_WIDTH; + tableWrapData.maxHeight = CONTENT_CONTROL_MAX_HEIGHT; + table.setLayoutData(tableWrapData); + + for (int i = 0; i < cols; i++) { + TableColumn tableColumn = new TableColumn(table, SWT.LEFT); + tableColumn.setWidth(CONTENT_CONTROL_MAX_WIDTH / cols - 10); + tableColumn.setResizable(true); + if (i < headers.length) { + tableColumn.setText(headers[i]); + } + } + + for (String[] row : rows) { + new TableItem(table, SWT.NONE).setText(row); + + copyStringBuilder.append('\t'); + for (int i = 0; i < row.length; i++) { + copyStringBuilder.append(row[i]); + if (i < row.length - 1) { + copyStringBuilder.append('\t'); + } + } + copyStringBuilder.append('\n'); + } + } + + /** + * @param cellContent + * {@link DetailsCellContent} + * @return Returns the {@link TableWrapData} based in the information in the + * {@link DetailsCellContent}. + */ + private TableWrapData getLayoutData(DetailsCellContent cellContent) { + TableWrapData tableWrapData = new TableWrapData(); + tableWrapData.colspan = cellContent.getColspan(); + tableWrapData.grabHorizontal = cellContent.isGrab(); + return tableWrapData; + } + + /** + * Fills {@link FormText} with content based on the {@link DetailsCellContent}. + * + * @param formText + * {@link FormText} to fill. + * @param cellContent + * {@link DetailsCellContent} describing the content. + */ + private void fillFormText(FormText formText, DetailsCellContent cellContent) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("

"); + if (null != cellContent.getText()) { + String text = StringUtils.replaceEach(cellContent.getText(), new String[] { "<", ">", "&" }, new String[] { "<", ">", "&" }); + stringBuilder.append(text); + } + + if (null != cellContent.getImage()) { + Label label = new Label(formText, SWT.NONE); + label.setImage(cellContent.getImage()); + if (null != cellContent.getImageToolTip()) { + label.setToolTipText(cellContent.getImageToolTip()); + } + stringBuilder.append(""); + formText.setControl("ctrl", label); + } + stringBuilder.append("

"); + formText.setText(stringBuilder.toString(), true, false); + } + + /** + * Initializes the {@link #contentComposite}. + */ + private void initTable() { + contentComposite = toolkit.createComposite(getSection()); + contentComposite.setLayoutData(new TableWrapData(TableWrapData.FILL)); + TableWrapLayout layout = new TableWrapLayout(); + layout.numColumns = columns + 1; + layout.horizontalSpacing = 2; + layout.verticalSpacing = 2; + contentComposite.setLayout(layout); + + getSection().setClient(contentComposite); + } + + /** + * Creates header. + * + * @param heading + * Heading to use. + */ + private void createHeading(String heading) { + getSection().setText(heading); + } + + /** + * Creates row heading. + * + * @param title + * Title to use. + * @param image + * Image to use. Can be null. + */ + private void createRowHeading(String title, Image image) { + // first create help composite + Composite composite = toolkit.createComposite(contentComposite); + composite.setLayoutData(new TableWrapData(TableWrapData.FILL)); + GridLayout compositeLayout = new GridLayout(1, true); + compositeLayout.marginHeight = 0; + compositeLayout.marginBottom = 0; + compositeLayout.horizontalSpacing = 0; + compositeLayout.verticalSpacing = 0; + composite.setLayout(compositeLayout); + + // create row text in bold + FormText rowText = toolkit.createFormText(composite, false); + if (null != image) { + rowText.setText("

" + title + "

", true, false); + rowText.setImage("img", image); + } else { + rowText.setText("

" + title + "

", true, false); + } + rowText.setColor("headingColor", toolkit.getColors().getColor(IFormColors.TITLE)); + + GridData rowTextGridData = getFixedWidthGridData(ROW_TITLE_WIDTH_HINT); + rowTextGridData.verticalAlignment = SWT.TOP; + rowText.setLayoutData(rowTextGridData); + } + + /** + * Creates {@link GridData} with fixed width. + * + * @param width + * wanted width. + * @return {@link GridData} + */ + private GridData getFixedWidthGridData(int width) { + GridData gridData = new GridData(); + gridData.minimumWidth = width; + gridData.widthHint = width; + return gridData; + } + + /** + * Returns String of table content for copying purposes. + * + * @return Returns String of table content for copying purposes. + */ + public String getCopyString() { + return copyStringBuilder.toString(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/details/YesNoDetailsCellContent.java b/inspectIT/src/info/novatec/inspectit/rcp/details/YesNoDetailsCellContent.java new file mode 100644 index 000000000..220614aad --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/details/YesNoDetailsCellContent.java @@ -0,0 +1,31 @@ +package info.novatec.inspectit.rcp.details; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; + +/** + * This {@link DetailsCellContent} sub-class displays Yes/No image with tool-tip based the boolean + * value. + * + * @author Ivan Senic + * + */ +public class YesNoDetailsCellContent extends DetailsCellContent { + + /** + * Default constructor. + * + * @param value + * Boolean value to display. + */ + public YesNoDetailsCellContent(boolean value) { + if (value) { + setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_CHECKMARK)); + setImageToolTip("Yes"); + } else { + setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_CLOSE)); + setImageToolTip("No"); + } + + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/details/generator/IDetailsGenerator.java b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/IDetailsGenerator.java new file mode 100644 index 000000000..ac8a59636 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/IDetailsGenerator.java @@ -0,0 +1,44 @@ +package info.novatec.inspectit.rcp.details.generator; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.details.DetailsTable; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * Interface for details generator. + *

+ * Each generator generates one composite. + * + * @author Ivan Senic + * + */ +public interface IDetailsGenerator { + + /** + * Specifies if generator can generate composite for given {@link DefaultData} object. + * + * @param defaultData + * {@link DefaultData}. + * @return Return true if generator can generate the details for the + * {@link DefaultData} object. + */ + boolean canGenerateFor(DefaultData defaultData); + + /** + * Creates details composite on the given parent composite for the {@link DefaultData} object. + * + * @param defaultData + * {@link DefaultData} + * @param repositoryDefinition + * Repository definition data belongs to. + * @param parent + * Parent {@link Composite} + * @param toolkit + * {@link FormToolkit} + * @return Returns the created composite. + */ + DetailsTable generate(DefaultData defaultData, RepositoryDefinition repositoryDefinition, Composite parent, FormToolkit toolkit); +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/AggregatedDurationDetailsGenerator.java b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/AggregatedDurationDetailsGenerator.java new file mode 100644 index 000000000..18ed3f7f6 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/AggregatedDurationDetailsGenerator.java @@ -0,0 +1,71 @@ +package info.novatec.inspectit.rcp.details.generator.impl; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.IAggregatedData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.details.DetailsCellContent; +import info.novatec.inspectit.rcp.details.DetailsTable; +import info.novatec.inspectit.rcp.details.generator.IDetailsGenerator; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * Generator that displays the duration info from the {@link TimerData} and it's subclasses. + * + * @author Ivan Senic + * + */ +public class AggregatedDurationDetailsGenerator implements IDetailsGenerator { + + /** + * {@inheritDoc} + *

+ * Only for aggregated data. + */ + @Override + public boolean canGenerateFor(DefaultData defaultData) { + return defaultData instanceof TimerData && defaultData instanceof IAggregatedData; + } + + /** + * {@inheritDoc} + */ + @Override + public DetailsTable generate(DefaultData defaultData, RepositoryDefinition repositoryDefinition, Composite parent, FormToolkit toolkit) { + TimerData timerData = (TimerData) defaultData; + + DetailsTable table = new DetailsTable(parent, toolkit, "Duration Info", 4); + + // first headings + table.addContentRow("", null, new DetailsCellContent[] { new DetailsCellContent("Count"), new DetailsCellContent("Avg (ms)"), new DetailsCellContent("Min (ms)"), + new DetailsCellContent("Max (ms)") }); + + // then the total duration + DetailsCellContent[] total = new DetailsCellContent[] { new DetailsCellContent(NumberFormatter.formatLong(timerData.getCount())), + new DetailsCellContent(NumberFormatter.formatDouble(timerData.getAverage())), new DetailsCellContent(NumberFormatter.formatDouble(timerData.getMin())), + new DetailsCellContent(NumberFormatter.formatDouble(timerData.getMax())) }; + table.addContentRow("Total:", null, total); + + if (timerData.isCpuMetricDataAvailable()) { + // then the total duration + DetailsCellContent[] cpu = new DetailsCellContent[] { new DetailsCellContent(NumberFormatter.formatLong(timerData.getCount())), + new DetailsCellContent(NumberFormatter.formatDouble(timerData.getCpuAverage())), new DetailsCellContent(NumberFormatter.formatDouble(timerData.getCpuMin())), + new DetailsCellContent(NumberFormatter.formatDouble(timerData.getCpuMax())) }; + table.addContentRow("CPU:", null, cpu); + } + + if (timerData.isExclusiveTimeDataAvailable()) { + // then the exclusive + DetailsCellContent[] exclusive = new DetailsCellContent[] { new DetailsCellContent(NumberFormatter.formatLong(timerData.getCount())), + new DetailsCellContent(NumberFormatter.formatDouble(timerData.getExclusiveAverage())), new DetailsCellContent(NumberFormatter.formatDouble(timerData.getExclusiveMin())), + new DetailsCellContent(NumberFormatter.formatDouble(timerData.getExclusiveMax())) }; + table.addContentRow("Exclusive:", null, exclusive); + } + + return table; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/DurationDetailsGenerator.java b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/DurationDetailsGenerator.java new file mode 100644 index 000000000..652993aec --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/DurationDetailsGenerator.java @@ -0,0 +1,62 @@ +package info.novatec.inspectit.rcp.details.generator.impl; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.IAggregatedData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.details.DetailsCellContent; +import info.novatec.inspectit.rcp.details.DetailsTable; +import info.novatec.inspectit.rcp.details.generator.IDetailsGenerator; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * Generator that displays the duration info from the {@link TimerData} and it's subclasses. + * + * @author Ivan Senic + * + */ +public class DurationDetailsGenerator implements IDetailsGenerator { + + /** + * {@inheritDoc} + *

+ * Display for non-aggregated {@link TimerData} or {@link InvocationSequenceData}. + */ + @Override + public boolean canGenerateFor(DefaultData defaultData) { + return defaultData instanceof TimerData && !(defaultData instanceof IAggregatedData); + } + + /** + * {@inheritDoc} + */ + @Override + public DetailsTable generate(DefaultData defaultData, RepositoryDefinition repositoryDefinition, Composite parent, FormToolkit toolkit) { + TimerData timerData = (TimerData) defaultData; + + DetailsTable table = new DetailsTable(parent, toolkit, "Duration Info", 1); + + // then the total duration + DetailsCellContent[] total = new DetailsCellContent[] { new DetailsCellContent(NumberFormatter.formatDouble(timerData.getDuration())) }; + table.addContentRow("Total (ms):", null, total); + + if (timerData.isCpuMetricDataAvailable()) { + // then the total duration + DetailsCellContent[] cpu = new DetailsCellContent[] { new DetailsCellContent(NumberFormatter.formatDouble(timerData.getCpuDuration())) }; + table.addContentRow("CPU (ms):", null, cpu); + } + + if (timerData.isExclusiveTimeDataAvailable()) { + // then the exclusive + DetailsCellContent[] exclusive = new DetailsCellContent[] { new DetailsCellContent(NumberFormatter.formatDouble(timerData.getExclusiveDuration())) }; + table.addContentRow("Exclusive (ms):", null, exclusive); + } + + return table; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/ExceptionDetailsGenerator.java b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/ExceptionDetailsGenerator.java new file mode 100644 index 000000000..3d98c1bba --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/ExceptionDetailsGenerator.java @@ -0,0 +1,59 @@ +package info.novatec.inspectit.rcp.details.generator.impl; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.rcp.details.DetailsCellContent; +import info.novatec.inspectit.rcp.details.DetailsTable; +import info.novatec.inspectit.rcp.details.generator.IDetailsGenerator; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * Generator for the exception details. + * + * @author Ivan Senic + * + */ +public class ExceptionDetailsGenerator implements IDetailsGenerator { + + /** + * {@inheritDoc} + */ + @Override + public boolean canGenerateFor(DefaultData defaultData) { + return defaultData instanceof ExceptionSensorData; + } + + /** + * {@inheritDoc} + */ + @Override + public DetailsTable generate(DefaultData defaultData, RepositoryDefinition repositoryDefinition, Composite parent, FormToolkit toolkit) { + ExceptionSensorData exceptionSensorData = (ExceptionSensorData) defaultData; + + DetailsTable table = new DetailsTable(parent, toolkit, "Exception Info", 1); + + table.addContentRow("Type:", null, new DetailsCellContent[] { new DetailsCellContent(exceptionSensorData.getThrowableType()) }); + + if (null != exceptionSensorData.getExceptionEvent()) { + table.addContentRow("Event:", null, new DetailsCellContent[] { new DetailsCellContent(exceptionSensorData.getExceptionEvent().toString()) }); + } + + if (null != exceptionSensorData.getErrorMessage()) { + table.addContentRow("Error Message:", null, new DetailsCellContent[] { new DetailsCellContent(exceptionSensorData.getErrorMessage()) }); + } + + if (null != exceptionSensorData.getCause()) { + table.addContentRow("Cause:", null, new DetailsCellContent[] { new DetailsCellContent(exceptionSensorData.getCause()) }); + } + + if (null != exceptionSensorData.getStackTrace()) { + table.addContentRow("Stack Trace:", null, new DetailsCellContent[] { new DetailsCellContent(exceptionSensorData.getStackTrace(), true) }); + } + + return table; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/GeneralInfoDetailsGenerator.java b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/GeneralInfoDetailsGenerator.java new file mode 100644 index 000000000..725f39931 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/GeneralInfoDetailsGenerator.java @@ -0,0 +1,47 @@ +package info.novatec.inspectit.rcp.details.generator.impl; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.details.DetailsCellContent; +import info.novatec.inspectit.rcp.details.DetailsTable; +import info.novatec.inspectit.rcp.details.generator.IDetailsGenerator; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * The general info information. Currently only displaying the time-stamp. + * + * @author Ivan Senic + * + */ +public class GeneralInfoDetailsGenerator implements IDetailsGenerator { + + /** + * {@inheritDoc} + */ + @Override + public boolean canGenerateFor(DefaultData defaultData) { + return null != defaultData.getTimeStamp(); + } + + /** + * {@inheritDoc} + */ + @Override + public DetailsTable generate(DefaultData defaultData, RepositoryDefinition repositoryDefinition, Composite parent, FormToolkit toolkit) { + DetailsTable table = new DetailsTable(parent, toolkit, "General Info", 1); + + // time stamp + if (null != defaultData.getTimeStamp()) { + table.addContentRow("Timestamp:", InspectIT.getDefault().getImage(InspectITImages.IMG_CALENDAR), + new DetailsCellContent[] { new DetailsCellContent(NumberFormatter.formatTime(defaultData.getTimeStamp().getTime())) }); + } + + return table; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/HttpDetailsGenerator.java b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/HttpDetailsGenerator.java new file mode 100644 index 000000000..496ef46b0 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/HttpDetailsGenerator.java @@ -0,0 +1,90 @@ +package info.novatec.inspectit.rcp.details.generator.impl; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.rcp.details.DetailsCellContent; +import info.novatec.inspectit.rcp.details.DetailsTable; +import info.novatec.inspectit.rcp.details.generator.IDetailsGenerator; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections.MapUtils; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * HTTP details generator. Displays information like URI, request method, parameters, attributes, + * etc. + * + * @author Ivan Senic + * + */ +public class HttpDetailsGenerator implements IDetailsGenerator { + + /** + * {@inheritDoc} + */ + @Override + public boolean canGenerateFor(DefaultData defaultData) { + return defaultData instanceof HttpTimerData; + } + + /** + * {@inheritDoc} + */ + @Override + public DetailsTable generate(DefaultData defaultData, RepositoryDefinition repositoryDefinition, Composite parent, FormToolkit toolkit) { + HttpTimerData httpTimerData = (HttpTimerData) defaultData; + + DetailsTable table = new DetailsTable(parent, toolkit, "HTTP Info", 1); + + table.addContentRow("Method:", null, new DetailsCellContent[] { new DetailsCellContent(httpTimerData.getRequestMethod()) }); + table.addContentRow("URI:", null, new DetailsCellContent[] { new DetailsCellContent(httpTimerData.getUri()) }); + + if (httpTimerData.hasInspectItTaggingHeader()) { + table.addContentRow("Tag Value:", null, new DetailsCellContent[] { new DetailsCellContent(httpTimerData.getInspectItTaggingHeaderValue()) }); + } + + // parameters + if (MapUtils.isNotEmpty(httpTimerData.getParameters())) { + List rows = new ArrayList<>(); + for (Map.Entry entry : httpTimerData.getParameters().entrySet()) { + rows.add(new String[] { entry.getKey(), Arrays.toString(entry.getValue()) }); + } + table.addContentTable("Parameters:", null, 2, new String[] { "Parameter", "Value" }, rows); + } + + // attributes + if (MapUtils.isNotEmpty(httpTimerData.getAttributes())) { + List rows = new ArrayList<>(); + for (Map.Entry entry : httpTimerData.getAttributes().entrySet()) { + rows.add(new String[] { entry.getKey(), entry.getValue() }); + } + table.addContentTable("Attributes:", null, 2, new String[] { "Attribute", "Value" }, rows); + } + + // headers + if (MapUtils.isNotEmpty(httpTimerData.getHeaders())) { + List rows = new ArrayList<>(); + for (Map.Entry entry : httpTimerData.getHeaders().entrySet()) { + rows.add(new String[] { entry.getKey(), entry.getValue() }); + } + table.addContentTable("Headers:", null, 2, new String[] { "Header", "Value" }, rows); + } + + // session attributes + if (MapUtils.isNotEmpty(httpTimerData.getSessionAttributes())) { + List rows = new ArrayList<>(); + for (Map.Entry entry : httpTimerData.getSessionAttributes().entrySet()) { + rows.add(new String[] { entry.getKey(), entry.getValue() }); + } + table.addContentTable("Session Attributes:", null, 2, new String[] { "Session Attribute", "Value" }, rows); + } + + return table; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/InvocationAffiliationDetailsGenerator.java b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/InvocationAffiliationDetailsGenerator.java new file mode 100644 index 000000000..b43f20476 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/InvocationAffiliationDetailsGenerator.java @@ -0,0 +1,55 @@ +package info.novatec.inspectit.rcp.details.generator.impl; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.IAggregatedData; +import info.novatec.inspectit.communication.data.InvocationAwareData; +import info.novatec.inspectit.rcp.details.DetailsCellContent; +import info.novatec.inspectit.rcp.details.DetailsTable; +import info.novatec.inspectit.rcp.details.YesNoDetailsCellContent; +import info.novatec.inspectit.rcp.details.generator.IDetailsGenerator; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * Details about invocation affiliation read from the {@link InvocationAwareData}. + * + * @author Ivan Senic + * + */ +public class InvocationAffiliationDetailsGenerator implements IDetailsGenerator { + + /** + * {@inheritDoc} + *

+ * Display only for aggregated data. + */ + @Override + public boolean canGenerateFor(DefaultData defaultData) { + return defaultData instanceof InvocationAwareData && defaultData instanceof IAggregatedData; + } + + /** + * {@inheritDoc} + */ + @Override + public DetailsTable generate(DefaultData defaultData, RepositoryDefinition repositoryDefinition, Composite parent, FormToolkit toolkit) { + InvocationAwareData invocationAwareData = (InvocationAwareData) defaultData; + + DetailsTable table = new DetailsTable(parent, toolkit, "Invocation Affiliation", 1); + + table.addContentRow("In Invocations:", null, new DetailsCellContent[] { new YesNoDetailsCellContent(!invocationAwareData.isOnlyFoundOutsideInvocations()) }); + + if (!invocationAwareData.isOnlyFoundOutsideInvocations()) { + int percentage = (int) (invocationAwareData.getInvocationAffiliationPercentage() * 100); + int invocations = invocationAwareData.getInvocationParentsIdSet().size(); + String affiliation = TextFormatter.getInvocationAffilliationPercentageString(percentage, invocations).getString(); + table.addContentRow("Affiliation:", null, new DetailsCellContent[] { new DetailsCellContent(affiliation) }); + } + + return table; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/InvocationSequenceDetailsGenerator.java b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/InvocationSequenceDetailsGenerator.java new file mode 100644 index 000000000..9b3589d9d --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/InvocationSequenceDetailsGenerator.java @@ -0,0 +1,49 @@ +package info.novatec.inspectit.rcp.details.generator.impl; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.InvocationSequenceDataHelper; +import info.novatec.inspectit.rcp.details.DetailsCellContent; +import info.novatec.inspectit.rcp.details.DetailsTable; +import info.novatec.inspectit.rcp.details.YesNoDetailsCellContent; +import info.novatec.inspectit.rcp.details.generator.IDetailsGenerator; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * Details of the {@link InvocationSequenceData}. + * + * @author Ivan Senic + * + */ +public class InvocationSequenceDetailsGenerator implements IDetailsGenerator { + + /** + * {@inheritDoc} + *

+ * Display only for root invocations. + */ + @Override + public boolean canGenerateFor(DefaultData defaultData) { + return defaultData instanceof InvocationSequenceData && ((InvocationSequenceData) defaultData).getParentSequence() == null; + } + + /** + * {@inheritDoc} + */ + @Override + public DetailsTable generate(DefaultData defaultData, RepositoryDefinition repositoryDefinition, Composite parent, FormToolkit toolkit) { + InvocationSequenceData invocationSequenceData = (InvocationSequenceData) defaultData; + + DetailsTable table = new DetailsTable(parent, toolkit, "Invocation Sequence Info", 1); + + table.addContentRow("Children Count:", null, new DetailsCellContent[] { new DetailsCellContent(String.valueOf(invocationSequenceData.getChildCount())) }); + table.addContentRow("Nested SQLs:", null, new DetailsCellContent[] { new YesNoDetailsCellContent(InvocationSequenceDataHelper.hasNestedSqlStatements(invocationSequenceData)) }); + table.addContentRow("Nested Exceptions:", null, new DetailsCellContent[] { new YesNoDetailsCellContent(InvocationSequenceDataHelper.hasNestedExceptions(invocationSequenceData)) }); + + return table; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/MethodInfoDetailsGenerator.java b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/MethodInfoDetailsGenerator.java new file mode 100644 index 000000000..cd20a168e --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/MethodInfoDetailsGenerator.java @@ -0,0 +1,80 @@ +package info.novatec.inspectit.rcp.details.generator.impl; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.details.DetailsCellContent; +import info.novatec.inspectit.rcp.details.DetailsTable; +import info.novatec.inspectit.rcp.details.generator.IDetailsGenerator; +import info.novatec.inspectit.rcp.model.ModifiersImageFactory; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * Generates information about the method from the {@link MethodSensorData}. + * + * @author Ivan Senic + * + */ +public class MethodInfoDetailsGenerator implements IDetailsGenerator { + + /** + * {@inheritDoc} + */ + @Override + public boolean canGenerateFor(DefaultData defaultData) { + return defaultData instanceof MethodSensorData && ((MethodSensorData) defaultData).getMethodIdent() != 0; + } + + /** + * {@inheritDoc} + */ + @Override + public DetailsTable generate(DefaultData defaultData, RepositoryDefinition repositoryDefinition, Composite parent, FormToolkit toolkit) { + MethodSensorData methodSensorData = (MethodSensorData) defaultData; + MethodIdent methodIdent = repositoryDefinition.getCachedDataService().getMethodIdentForId(methodSensorData.getMethodIdent()); + + DetailsTable table = new DetailsTable(parent, toolkit, "Method Info", 1); + + // package & class + table.addContentRow("Package:", InspectIT.getDefault().getImage(InspectITImages.IMG_PACKAGE), new DetailsCellContent[] { new DetailsCellContent(methodIdent.getPackageName()) }); + table.addContentRow("Class:", InspectIT.getDefault().getImage(InspectITImages.IMG_CLASS), new DetailsCellContent[] { new DetailsCellContent(methodIdent.getClassName()) }); + + // method + String params = ""; + if (CollectionUtils.isNotEmpty(methodIdent.getParameters())) { + params = methodIdent.getParameters().toString(); + params = params.substring(1, params.length() - 1); + } + String method = methodIdent.getMethodName() + "(" + params + ")"; + table.addContentRow("Method:", ModifiersImageFactory.getImage(methodIdent.getModifiers()), new DetailsCellContent[] { new DetailsCellContent(method) }); + + // return type + String returnType = methodIdent.getReturnType(); + if (StringUtils.isBlank(returnType)) { + returnType = "void"; + } + table.addContentRow("Return Type:", null, new DetailsCellContent[] { new DetailsCellContent(returnType) }); + + // instrumentation + DetailsCellContent instrumentationContent = new DetailsCellContent(); + instrumentationContent.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_INFORMATION)); + if (methodIdent.hasActiveSensorTypes()) { + instrumentationContent.setText("Active "); + instrumentationContent.setImageToolTip("Method is currently instrumented and captures data."); + } else { + instrumentationContent.setText("Not-active "); + instrumentationContent.setImageToolTip("Method is currently not instrumented and doesn't capture data."); + } + table.addContentRow("Instrumentation:", null, new DetailsCellContent[] { instrumentationContent }); + + return table; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/ParameterContentDetailsGenerator.java b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/ParameterContentDetailsGenerator.java new file mode 100644 index 000000000..03ef65a0f --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/ParameterContentDetailsGenerator.java @@ -0,0 +1,104 @@ +package info.novatec.inspectit.rcp.details.generator.impl; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.communication.data.ParameterContentData; +import info.novatec.inspectit.communication.data.ParameterContentType; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.details.DetailsTable; +import info.novatec.inspectit.rcp.details.generator.IDetailsGenerator; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * Details generator for the parameter content data in the {@link MethodSensorData}. + * + * @author Ivan Senic + * + */ +public class ParameterContentDetailsGenerator implements IDetailsGenerator { + + /** + * {@inheritDoc} + */ + @Override + public boolean canGenerateFor(DefaultData defaultData) { + return defaultData instanceof MethodSensorData && CollectionUtils.isNotEmpty(((MethodSensorData) defaultData).getParameterContentData()); + } + + /** + * {@inheritDoc} + */ + @Override + public DetailsTable generate(DefaultData defaultData, RepositoryDefinition repositoryDefinition, Composite parent, FormToolkit toolkit) { + Map> contentMap = getContentTypeMap(((MethodSensorData) defaultData).getParameterContentData()); + + DetailsTable table = new DetailsTable(parent, toolkit, "Parameter Content Data", 1); + + for (Map.Entry> entry : contentMap.entrySet()) { + List rows = new ArrayList<>(); + for (ParameterContentData data : entry.getValue()) { + rows.add(new String[] { data.getName(), data.getContent() }); + } + String heading = StringUtils.capitalize(entry.getKey().toString().toLowerCase()) + ":"; + table.addContentTable(heading, getImageForParameterContentType(entry.getKey()), 2, new String[] { "Name", "Value" }, rows); + } + + return table; + } + + /** + * Returns map of the {@link ParameterContentData} divided by the {@link ParameterContentType}s. + * + * @param parameterContentDatas + * Data to divide in groups. + * @return Map> + */ + private Map> getContentTypeMap(Collection parameterContentDatas) { + if (CollectionUtils.isEmpty(parameterContentDatas)) { + return Collections.emptyMap(); + } + + Map> map = new HashMap>(); + for (ParameterContentData data : parameterContentDatas) { + Collection collection = map.get(data.getContentType()); + if (null == collection) { + collection = new ArrayList<>(1); + map.put(data.getContentType(), collection); + } + collection.add(data); + } + return map; + } + + /** + * Returns icon for {@link ParameterContentType}. + * + * @param parameterContentType + * {@link ParameterContentType}. + * @return Returns icon for {@link ParameterContentType}. + */ + private Image getImageForParameterContentType(ParameterContentType parameterContentType) { + if (parameterContentType == ParameterContentType.FIELD) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_FIELD); + } else if (parameterContentType == ParameterContentType.PARAM) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_PARAMETER); + } else if (parameterContentType == ParameterContentType.RETURN) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_RETURN); + } + return null; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/SqlDetailsGenerator.java b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/SqlDetailsGenerator.java new file mode 100644 index 000000000..ac1d165d5 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/details/generator/impl/SqlDetailsGenerator.java @@ -0,0 +1,47 @@ +package info.novatec.inspectit.rcp.details.generator.impl; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.rcp.details.DetailsCellContent; +import info.novatec.inspectit.rcp.details.DetailsTable; +import info.novatec.inspectit.rcp.details.YesNoDetailsCellContent; +import info.novatec.inspectit.rcp.details.generator.IDetailsGenerator; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * SQL information details generator. + * + * @author Ivan Senic + * + */ +public class SqlDetailsGenerator implements IDetailsGenerator { + + /** + * {@inheritDoc} + */ + @Override + public boolean canGenerateFor(DefaultData defaultData) { + return defaultData instanceof SqlStatementData; + } + + /** + * {@inheritDoc} + */ + @Override + public DetailsTable generate(DefaultData defaultData, RepositoryDefinition repositoryDefinition, Composite parent, FormToolkit toolkit) { + SqlStatementData sqlStatementData = (SqlStatementData) defaultData; + + DetailsTable table = new DetailsTable(parent, toolkit, "SQL Info", 1); + + table.addContentRow("Is Prepared:", null, new DetailsCellContent[] { new YesNoDetailsCellContent(sqlStatementData.isPreparedStatement()) }); + table.addContentRow("Database:", null, new DetailsCellContent[] { new DetailsCellContent(sqlStatementData.getDatabaseProductName()) }); + table.addContentRow("Database version:", null, new DetailsCellContent[] { new DetailsCellContent(sqlStatementData.getDatabaseProductVersion()) }); + table.addContentRow("Database URL:", null, new DetailsCellContent[] { new DetailsCellContent(sqlStatementData.getDatabaseUrl()) }); + table.addContentRow("SQL:", null, new DetailsCellContent[] { new DetailsCellContent(sqlStatementData.getSqlWithParameterValues(), true) }); + return table; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/dialog/AddCmrRepositoryDefinitionDialog.java b/inspectIT/src/info/novatec/inspectit/rcp/dialog/AddCmrRepositoryDefinitionDialog.java new file mode 100644 index 000000000..f7041fe96 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/dialog/AddCmrRepositoryDefinitionDialog.java @@ -0,0 +1,283 @@ +package info.novatec.inspectit.rcp.dialog; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.internal.forms.widgets.BusyIndicator; +import org.eclipse.ui.progress.IProgressConstants; + +/** + * Dialog for add repository definition action. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("restriction") +public class AddCmrRepositoryDefinitionDialog extends TitleAreaDialog { + + /** + * Name text box. + */ + private Text nameBox; + + /** + * IP text box. + */ + private Text ipBox; + + /** + * Port text box. + */ + private Text portBox; + + /** + * Description box. + */ + private Text descriptionBox; + + /** + * {@link CmrRepositoryDefinition}. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition = null; + + /** + * OK button. + */ + private Button okButton; + + /** + * Default constructor. + * + * @param parentShell + * Shell. + */ + public AddCmrRepositoryDefinitionDialog(Shell parentShell) { + super(parentShell); + setDefaultImage(InspectIT.getDefault().getImage(InspectITImages.IMG_WIZBAN_SERVER)); + } + + /** + * {@inheritDoc} + */ + @Override + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + newShell.setText("Add CMR Repository Definition"); + } + + /** + * {@inheritDoc} + */ + @Override + public void create() { + super.create(); + this.setTitle("Add CMR Repository Definition"); + this.setMessage("Define information for the repository to add", IMessageProvider.INFORMATION); + } + + /** + * {@inheritDoc} + */ + @Override + protected Control createDialogArea(Composite parent) { + Composite main = new Composite(parent, SWT.NONE); + main.setLayout(new GridLayout(4, false)); + GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true); + gd.minimumWidth = 400; + gd.minimumHeight = 250; + main.setLayoutData(gd); + + Label nameLabel = new Label(main, SWT.LEFT); + nameLabel.setText("Server name:"); + nameBox = new Text(main, SWT.BORDER); + nameBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1)); + + Label ipLabel = new Label(main, SWT.LEFT); + ipLabel.setText("IP Address:"); + ipBox = new Text(main, SWT.BORDER); + ipBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + ipBox.setText(CmrRepositoryDefinition.DEFAULT_IP); + + Label portLabel = new Label(main, SWT.LEFT); + portLabel.setText("Port:"); + portBox = new Text(main, SWT.BORDER); + portBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + portBox.setText(String.valueOf(CmrRepositoryDefinition.DEFAULT_PORT)); + + Label descLabel = new Label(main, SWT.LEFT); + descLabel.setText("Description:"); + descLabel.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + descriptionBox = new Text(main, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.WRAP); + descriptionBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1)); + + new Label(main, SWT.LEFT); + final Button testConnection = new Button(main, SWT.PUSH); + testConnection.setText("Test connection"); + testConnection.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + + final BusyIndicator busyIndicator = new BusyIndicator(main, SWT.NONE); + busyIndicator.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + + final Label testLabel = new Label(main, SWT.LEFT); + testLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + + testConnection.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + testConnection.setEnabled(false); + testLabel.setText("Testing.."); + busyIndicator.setBusy(true); + final String ip = ipBox.getText().trim(); + final int port = Integer.parseInt(portBox.getText()); + Job checkCmr = new Job("Checking online status..") { + @Override + public IStatus run(IProgressMonitor monitor) { + CmrRepositoryDefinition cmr = new CmrRepositoryDefinition(ip, port); + boolean testOk = false; + try { + cmr.refreshOnlineStatus(); + testOk = cmr.getOnlineStatus() == OnlineStatus.ONLINE; + } catch (Exception exception) { + testOk = false; + } + final boolean testOkFinal = testOk; + Display.getDefault().asyncExec(new Runnable() { + public void run() { + if (!busyIndicator.isDisposed() && !testLabel.isDisposed()) { + if (busyIndicator.isBusy()) { + busyIndicator.setBusy(false); + } + if (testOkFinal) { + testLabel.setText("Succeeded"); + busyIndicator.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_CHECKMARK)); + } else { + testLabel.setText("Failed"); + busyIndicator.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_CLOSE)); + } + } + } + }); + return Status.OK_STATUS; + } + }; + checkCmr.setUser(false); + checkCmr.setProperty(IProgressConstants.ICON_PROPERTY, InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_SERVER_REFRESH_SMALL)); + checkCmr.schedule(); + } + }); + + ModifyListener modifyListener = new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + if (isInputValid()) { + okButton.setEnabled(true); + } else { + okButton.setEnabled(false); + } + } + }; + nameBox.addModifyListener(modifyListener); + ipBox.addModifyListener(modifyListener); + portBox.addModifyListener(modifyListener); + + ModifyListener testModifyListener = new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + if (!ipBox.getText().isEmpty() && !portBox.getText().isEmpty()) { + testConnection.setEnabled(true); + } else { + testConnection.setEnabled(false); + } + testLabel.setText(""); + busyIndicator.setImage(null); + if (busyIndicator.isBusy()) { + busyIndicator.setBusy(false); + } + } + }; + ipBox.addModifyListener(testModifyListener); + portBox.addModifyListener(testModifyListener); + + return main; + } + + /** + * {@inheritDoc} + */ + @Override + protected void createButtonsForButtonBar(Composite parent) { + createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CLOSE_LABEL, false); + okButton = createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); + okButton.setEnabled(false); + } + + /** + * {@inheritDoc} + */ + @Override + protected void buttonPressed(int buttonId) { + if (buttonId == IDialogConstants.OK_ID) { + cmrRepositoryDefinition = new CmrRepositoryDefinition(ipBox.getText().trim(), Integer.parseInt(portBox.getText()), nameBox.getText().trim()); + if (!descriptionBox.getText().trim().isEmpty()) { + cmrRepositoryDefinition.setDescription(descriptionBox.getText().trim()); + } else { + cmrRepositoryDefinition.setDescription(""); + } + } + super.buttonPressed(buttonId); + } + + /** + * @return the cmrRepositoryDefinition + */ + public CmrRepositoryDefinition getCmrRepositoryDefinition() { + return cmrRepositoryDefinition; + } + + /** + * Is input in textual boxes valid. + * + * @return Is input in textual boxes valid. + */ + private boolean isInputValid() { + if (nameBox.getText().isEmpty()) { + return false; + } + if (ipBox.getText().isEmpty()) { + return false; + } + if (portBox.getText().isEmpty()) { + return false; + } else { + try { + Integer.parseInt(portBox.getText()); + } catch (NumberFormatException e) { + return false; + } + } + return true; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/dialog/DetailsDialog.java b/inspectIT/src/info/novatec/inspectit/rcp/dialog/DetailsDialog.java new file mode 100644 index 000000000..782f0487f --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/dialog/DetailsDialog.java @@ -0,0 +1,360 @@ +package info.novatec.inspectit.rcp.dialog; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.cmr.model.SensorTypeIdent; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.composite.BreadcrumbTitleComposite; +import info.novatec.inspectit.rcp.details.DetailsGenerationFactory; +import info.novatec.inspectit.rcp.details.DetailsTable; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.model.SensorTypeEnum; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; +import info.novatec.inspectit.rcp.util.AccessibleArrowImage; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.core.commands.Command; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.forms.ManagedForm; +import org.eclipse.ui.forms.events.HyperlinkAdapter; +import org.eclipse.ui.forms.events.HyperlinkEvent; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Hyperlink; +import org.eclipse.ui.forms.widgets.ScrolledForm; +import org.eclipse.ui.forms.widgets.Section; +import org.eclipse.ui.forms.widgets.TableWrapData; +import org.eclipse.ui.forms.widgets.TableWrapLayout; + +/** + * The dialog that displays details of the element. + * + * @author Ivan Senic + * + */ +public class DetailsDialog extends Dialog { + + /** + * Maps of commands IDs and labels to display related to navigation. + */ + private static final Map NAVIGATE_COMMANDS_IDS = new HashMap<>(); + + /** + * Maps of commands IDs and labels to display related to navigation. + */ + private static final Map ACTION_COMMANDS_IDS = new HashMap<>(); + + static { + NAVIGATE_COMMANDS_IDS.put("info.novatec.inspectit.rcp.commands.navigateToAggregatedSqlData", "Aggregated SQL Data"); + NAVIGATE_COMMANDS_IDS.put("info.novatec.inspectit.rcp.commands.navigateToAggregatedTimerData", "Aggregated Timer Data"); + NAVIGATE_COMMANDS_IDS.put("info.novatec.inspectit.rcp.commands.navigateToInvocations", "Invocation(s)"); + NAVIGATE_COMMANDS_IDS.put("info.novatec.inspectit.rcp.commands.navigateToStartMethodInvocations", "Only This Method Invocation(s)"); + NAVIGATE_COMMANDS_IDS.put("info.novatec.inspectit.rcp.commands.navigateToSingleExceptionType", "Exception Type"); + NAVIGATE_COMMANDS_IDS.put("info.novatec.inspectit.rcp.commands.navigateToGroupedExceptionType", "Grouped Exception View"); + + ACTION_COMMANDS_IDS.put("info.novatec.inspectit.rcp.commands.copySqlQuery", "Copy SQL Query"); + ACTION_COMMANDS_IDS.put("info.novatec.inspectit.rcp.commands.displayInChart", "Display in Chart"); + ACTION_COMMANDS_IDS.put("org.eclipse.ui.file.save", "Save to Server"); + } + + /** + * Data to display the details for. + */ + private DefaultData defaultData; + + /** + * Repository definition data belongs to. + */ + private RepositoryDefinition repositoryDefinition; + + /** + * {@link ICommandService} for dealing with the commands to be displayed on the workflow menu. + */ + private ICommandService commandService; + + /** + * Command that will be executed when dialog is closed. + */ + private Command commandOnClose; + + /** + * Arrow image to display next to the command links. + */ + private Image arrow; + + /** + * {@link BreadcrumbTitleComposite} for displaying on the top. + */ + private BreadcrumbTitleComposite breadcrumbTitleComposite; + + /** + * List of created {@link DetailsTable}s. + */ + private List detailTables; + + /** + * Default constructor. + * + * @param parentShell + * {@link Shell} to create dialog on. + * @param defaultData + * {@link DefaultData} to create details for. + * @param repositoryDefinition + * {@link RepositoryDefinition} data belongs to. + */ + public DetailsDialog(Shell parentShell, DefaultData defaultData, RepositoryDefinition repositoryDefinition) { + super(parentShell); + this.defaultData = defaultData; + this.repositoryDefinition = repositoryDefinition; + commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + arrow = new AccessibleArrowImage(true).createImage(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + newShell.setText("Details"); + } + + /** + * {@inheritDoc} + */ + @Override + protected void createButtonsForButtonBar(Composite parent) { + createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); + } + + /** + * {@inheritDoc} + */ + @Override + protected void setShellStyle(int newShellStyle) { + super.setShellStyle(SWT.CLOSE | SWT.MODELESS | SWT.BORDER | SWT.TITLE | SWT.RESIZE); + setBlockOnOpen(false); + } + + /** + * {@inheritDoc} + */ + @Override + protected Control createDialogArea(Composite parent) { + Composite content = (Composite) super.createDialogArea(parent); + content.setLayout(new FillLayout()); + ((GridData) content.getLayoutData()).widthHint = 1050; + + ManagedForm managedForm = new ManagedForm(content); + FormToolkit toolkit = managedForm.getToolkit(); + ScrolledForm form = managedForm.getForm(); + managedForm.getToolkit().decorateFormHeading(form.getForm()); + + breadcrumbTitleComposite = new BreadcrumbTitleComposite(form.getForm().getHead(), SWT.NONE); + setDataForBreadcrumbTitleComposite(); + form.setHeadClient(breadcrumbTitleComposite); + + Composite main = form.getBody(); + TableWrapLayout mainLayout = new TableWrapLayout(); + mainLayout.numColumns = 2; + main.setLayout(mainLayout); + + // info section + Composite info = toolkit.createComposite(main); + info.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + TableWrapLayout infoLayout = new TableWrapLayout(); + infoLayout.verticalSpacing = 20; + info.setLayout(infoLayout); + + DetailsGenerationFactory generationFactory = InspectIT.getService(DetailsGenerationFactory.class); + detailTables = generationFactory.createDetailComposites(defaultData, repositoryDefinition, info, toolkit); + if (CollectionUtils.isNotEmpty(detailTables)) { + for (DetailsTable detailsTable : detailTables) { + detailsTable.getSection().setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + managedForm.addPart(detailsTable); + } + } + + // menu section + Composite navigation = toolkit.createComposite(main); + navigation.setData(defaultData); + navigation.setLayoutData(new TableWrapData(TableWrapData.FILL)); + navigation.setLayout(new GridLayout(1, false)); + + // navigate stuff + Section navigateSection = toolkit.createSection(navigation, Section.TITLE_BAR); + navigateSection.setText("Navigate To"); + navigateSection.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + Composite navigateComposite = toolkit.createComposite(navigateSection); + navigateComposite.setLayout(new GridLayout(2, false)); + navigateSection.setClient(navigateComposite); + + createLinks(navigateComposite, toolkit, NAVIGATE_COMMANDS_IDS); + + // actions stuff + Section actionsSection = toolkit.createSection(navigation, Section.TITLE_BAR); + actionsSection.setText("Actions"); + actionsSection.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + Composite actionsComposite = toolkit.createComposite(actionsSection); + actionsComposite.setLayout(new GridLayout(2, false)); + actionsSection.setClient(actionsComposite); + + // copy action manually + toolkit.createLabel(actionsComposite, "", SWT.NONE).setImage(arrow); + Hyperlink copyLink = toolkit.createHyperlink(actionsComposite, "Copy", SWT.WRAP); + copyLink.addHyperlinkListener(new CopyHyperlinkListener()); + copyLink.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + createLinks(actionsComposite, toolkit, ACTION_COMMANDS_IDS); + + return parent; + } + + /** + * Creates links for given commands on the parent composite. + * + * @param parent + * Parent composite. + * @param toolkit + * {@link FormToolkit} to use. + * @param commandMap + * IDs of the commands to display. Only active ones will be displayed. + */ + private void createLinks(Composite parent, FormToolkit toolkit, Map commandMap) { + for (Map.Entry entry : commandMap.entrySet()) { + Command command = commandService.getCommand(entry.getKey()); + if (command.isDefined() && null != command.getHandler() && command.getHandler().isEnabled()) { + toolkit.createLabel(parent, "", SWT.NONE).setImage(arrow); + + Hyperlink link = toolkit.createHyperlink(parent, entry.getValue(), SWT.WRAP); + link.addHyperlinkListener(new CommandHyperlinkListener(command)); + link.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + } + } + } + + /** + * Sets data for the {@link BreadcrumbTitleComposite}. + */ + private void setDataForBreadcrumbTitleComposite() { + // repository + breadcrumbTitleComposite.setRepositoryDefinition(repositoryDefinition); + + // agent + PlatformIdent platformIdent = repositoryDefinition.getCachedDataService().getPlatformIdentForId(defaultData.getPlatformIdent()); + breadcrumbTitleComposite.setAgent(TextFormatter.getAgentDescription(platformIdent), InspectIT.getDefault().getImage(InspectITImages.IMG_AGENT)); + + // sensor info + if (0 != defaultData.getSensorTypeIdent()) { + SensorTypeIdent sensorTypeIdent = repositoryDefinition.getCachedDataService().getSensorTypeIdentForId(defaultData.getSensorTypeIdent()); + String fqn = sensorTypeIdent.getFullyQualifiedClassName(); + SensorTypeEnum sensorTypeEnum = SensorTypeEnum.get(fqn); + if (null != sensorTypeEnum) { + breadcrumbTitleComposite.setGroup(sensorTypeEnum.getDisplayName(), sensorTypeEnum.getImage()); + } + } + + breadcrumbTitleComposite.setView("Details", null); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean close() { + arrow.dispose(); + breadcrumbTitleComposite.dispose(); + return super.close(); + } + + /** + * Gets {@link #commandOnClose}. + * + * @return {@link #commandOnClose} + */ + public Command getCommandOnClose() { + return commandOnClose; + } + + /** + * {@link HyperlinkAdapter} for setting the correct {@link DetailsDialog#commandOnClose} when + * clicked. + * + * @author Ivan Senic + * + */ + private class CommandHyperlinkListener extends HyperlinkAdapter { + + /** + * Command. + */ + private Command command; + + /** + * Default constructor. + * + * @param command + * Command. + */ + public CommandHyperlinkListener(Command command) { + this.command = command; + } + + /** + * {@inheritDoc} + */ + @Override + public void linkActivated(HyperlinkEvent e) { + DetailsDialog.this.commandOnClose = command; + DetailsDialog.this.close(); + } + } + + /** + * Hyperlink listener for the copy operation. + * + * @author Ivan Senic + * + */ + private class CopyHyperlinkListener extends HyperlinkAdapter { + + /** + * {@inheritDoc} + */ + @Override + public void linkActivated(HyperlinkEvent e) { + StringBuilder sb = new StringBuilder(); + + sb.append(breadcrumbTitleComposite.getCopyString()); + sb.append("\n\n"); + + for (DetailsTable table : detailTables) { + sb.append(table.getCopyString()); + sb.append('\n'); + } + + TextTransfer textTransfer = TextTransfer.getInstance(); + Clipboard cb = new Clipboard(getShell().getDisplay()); + cb.setContents(new Object[] { sb.toString() }, new Transfer[] { textTransfer }); + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/dialog/EditRepositoryDataDialog.java b/inspectIT/src/info/novatec/inspectit/rcp/dialog/EditRepositoryDataDialog.java new file mode 100644 index 000000000..6fb0b6562 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/dialog/EditRepositoryDataDialog.java @@ -0,0 +1,199 @@ +package info.novatec.inspectit.rcp.dialog; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.util.ObjectUtils; + +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** + * Dialog for editing a storage or CMR repository. + * + * @author Ivan Senic + * + */ +public class EditRepositoryDataDialog extends TitleAreaDialog { + + /** + * Name box. + */ + private Text nameBox; + + /** + * Description box. + */ + private Text descriptionBox; + + /** + * OK button. + */ + private Control okButton; + + /** + * Old name. + */ + private String oldName; + + /** + * Old description. + */ + private String oldDescription; + + /** + * New name. + */ + private String newDescription; + + /** + * New description. + */ + private String newName; + + /** + * Default constructor. + * + * @param parentShell + * Parent shell. + * @param oldName + * Old name. + * @param oldDescription + * Old description. + */ + public EditRepositoryDataDialog(Shell parentShell, String oldName, String oldDescription) { + super(parentShell); + this.oldName = oldName; + this.oldDescription = oldDescription; + } + + /** + * {@inheritDoc} + */ + @Override + public void create() { + super.create(); + this.setTitle("Edit Data"); + this.setMessage("Enter new name and/or description", IMessageProvider.INFORMATION); + this.setTitleImage(InspectIT.getDefault().getImage(InspectITImages.IMG_WIZBAN_EDIT)); + } + + /** + * {@inheritDoc} + */ + @Override + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + newShell.setText("Edit Data"); + } + + /** + * {@inheritDoc} + */ + @Override + protected Control createDialogArea(Composite parent) { + Composite main = new Composite(parent, SWT.NONE); + main.setLayout(new GridLayout(2, false)); + GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true); + gd.widthHint = 400; + gd.heightHint = 200; + main.setLayoutData(gd); + + Label nameLabel = new Label(main, SWT.LEFT); + nameLabel.setText("New name:"); + nameBox = new Text(main, SWT.BORDER); + nameBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + nameBox.setText(oldName); + nameBox.selectAll(); + + Label descLabel = new Label(main, SWT.LEFT); + descLabel.setText("New description:"); + descLabel.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + descriptionBox = new Text(main, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.WRAP); + descriptionBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + if (null != oldDescription) { + descriptionBox.setText(oldDescription); + } + + ModifyListener modifyListener = new ModifyListener() { + + @Override + public void modifyText(ModifyEvent e) { + if (isInputValid()) { + okButton.setEnabled(true); + } else { + okButton.setEnabled(false); + } + } + }; + + nameBox.addModifyListener(modifyListener); + descriptionBox.addModifyListener(modifyListener); + return main; + } + + /** + * {@inheritDoc} + */ + @Override + protected void createButtonsForButtonBar(Composite parent) { + createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CLOSE_LABEL, false); + okButton = createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); + okButton.setEnabled(false); + } + + /** + * {@inheritDoc} + */ + @Override + protected void buttonPressed(int buttonId) { + if (buttonId == IDialogConstants.OK_ID) { + newName = nameBox.getText().trim(); + if (!descriptionBox.getText().trim().isEmpty()) { + newDescription = descriptionBox.getText().trim(); + } + } + super.buttonPressed(buttonId); + } + + /** + * @return Returns the submitted name. + */ + public String getName() { + return newName; + } + + /** + * @return Returns the submitted description. + */ + public String getDescription() { + return newDescription; + } + + /** + * Is input in textual boxes valid. + * + * @return Is input in textual boxes valid. + */ + private boolean isInputValid() { + if (ObjectUtils.equals(oldName, nameBox.getText().trim())) { + if (ObjectUtils.equals(oldDescription, descriptionBox.getText().trim())) { + return false; + } + } + if (nameBox.getText().isEmpty()) { + return false; + } + return true; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/AbstractSubView.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/AbstractSubView.java new file mode 100644 index 000000000..f84fd9e9f --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/AbstractSubView.java @@ -0,0 +1,85 @@ +package info.novatec.inspectit.rcp.editor; + +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.Objects; + +import org.eclipse.core.runtime.Assert; + +/** + * Common abstract class for all sub-views. + * + * @author Patrice Bouillet + * + */ +public abstract class AbstractSubView implements ISubView { + + /** + * The root editor. + */ + private AbstractRootEditor rootEditor; + + /** + * {@inheritDoc} + */ + public void setRootEditor(AbstractRootEditor rootEditor) { + Assert.isNotNull(rootEditor); + + this.rootEditor = rootEditor; + } + + /** + * {@inheritDoc} + */ + public AbstractRootEditor getRootEditor() { + Assert.isNotNull(rootEditor); + + return rootEditor; + } + + /** + * {@inheritDoc} + *

+ * Default implementation does nothing. + */ + public void select(ISubView subView) { + } + + /** + * {@inheritDoc} + *

+ * Checks if the view is of the given class and if so returns itself. + */ + @SuppressWarnings("unchecked") + @Override + public E getSubView(Class clazz) { + if (Objects.equals(clazz, this.getClass())) { + return (E) this; + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public ISubView getSubViewWithInputController(Class inputControllerClass) { + return null; + } + + /** + * @return Returns the string for the data retrieving job. + */ + protected String getDataLoadingJobName() { + RepositoryDefinition repositoryDefinition = getRootEditor().getInputDefinition().getRepositoryDefinition(); + return "Retrieving data from " + repositoryDefinition.getName(); + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/ISubView.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/ISubView.java new file mode 100644 index 000000000..2176d5fd0 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/ISubView.java @@ -0,0 +1,146 @@ +package info.novatec.inspectit.rcp.editor; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; + +import java.util.List; +import java.util.Set; + +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * Interface used by all sub-views which are creating the final view. + * + * @author Patrice Bouillet + * + */ +public interface ISubView { + + /** + * Sets the root editor for this sub view. This is needed for event handling purposes or the + * access to the preference area. + * + * @param rootEditor + * The root editor. + */ + void setRootEditor(AbstractRootEditor rootEditor); + + /** + * Returns the root editor. + * + * @return The root editor. + */ + AbstractRootEditor getRootEditor(); + + /** + * Informs the sub-view has to do all initialization tasks. This method will be called after the + * {@link #setRootEditor(AbstractRootEditor)} has been executed, so that sub-view can get all + * necessary information from {@link AbstractRootEditor}. + */ + void init(); + + /** + * Creates the part control of this view. + * + * @param parent + * The parent used to draw the elements to. + * @param toolkit + * The form toolkit which is used for defining the colors of the widgets. Can be + * null to indicate that there is no toolkit. + */ + void createPartControl(Composite parent, FormToolkit toolkit); + + /** + * A sub-view should return all preference IDs itself is in need of and the ones of the children + * (it is a sub-view containing other views). + * + * @return A {@link Set} containing all {@link PreferenceId}. Returning null is not + * permitted here. At least a {@link java.util.Collections#EMPTY_SET} should be + * returned. + */ + Set getPreferenceIds(); + + /** + * Every sub-view contains some logic to retrieve the data on its own. This method invokes the + * refresh process which should update the view. + *

+ * For some views, it is possible that they do not show or do anything for default. + */ + void doRefresh(); + + /** + * This method is called whenever something is changed in one of the preferences. + * + * @param preferenceEvent + * The event object containing the changed objects. + */ + void preferenceEventFired(PreferenceEvent preferenceEvent); + + /** + * This will set the data input of the view. Every view can initialize itself with some data + * (like live data from the server). This is only needed if some specific needs to be displayed. + * + * @param data + * The list of {@link DefaultData} objects. + */ + void setDataInput(List data); + + /** + * Returns the control class of this view controller. + * + * @return The {@link Control} class. + */ + Control getControl(); + + /** + * Returns the selection provider for this view. + * + * @return The selection provider. + */ + ISelectionProvider getSelectionProvider(); + + /** + * Selects the given {@link ISubView} if it exists. The composite sub views should check if the + * given {@link ISubView} is one of the containing views and select it. Non-composite sub views + * should not do anything. + * + * @param subView + * {@link ISubView} to select. + */ + void select(ISubView subView); + + /** + * Returns the sub view of specific class. The composite sub views should check if the given + * {@link ISubView} is one of the given class and return it. Non-composite sub views should + * check if they are of the given class and return them self if so. + * + * @param + * Type of sub view. + * @param clazz + * Sub view class to search for. + * @return {@link ISubView} of given class or null if view can not be found. + */ + E getSubView(Class clazz); + + /** + * Returns the sub view that has given input controller class if exists. The composite sub views + * should check if the given {@link ISubView} is one of the given contained and return it. + * Non-composite sub views should check if they have the controller and return them self if so. + * + * @param inputControllerClass + * Class of the input controller that sub view to search for has. + * @return {@link ISubView} of or null if view can not be found. + */ + ISubView getSubViewWithInputController(Class inputControllerClass); + + /** + * Disposes this sub-view. + */ + void dispose(); + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/SubViewFactory.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/SubViewFactory.java new file mode 100644 index 000000000..3dd66c9c9 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/SubViewFactory.java @@ -0,0 +1,216 @@ +package info.novatec.inspectit.rcp.editor; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.composite.GridCompositeSubView; +import info.novatec.inspectit.rcp.editor.composite.SashCompositeSubView; +import info.novatec.inspectit.rcp.editor.composite.TabbedCompositeSubView; +import info.novatec.inspectit.rcp.editor.graph.GraphSubView; +import info.novatec.inspectit.rcp.editor.table.TableSubView; +import info.novatec.inspectit.rcp.editor.table.input.AggregatedTimerSummaryInputController; +import info.novatec.inspectit.rcp.editor.table.input.ExceptionSensorInvocInputController; +import info.novatec.inspectit.rcp.editor.table.input.GroupedExceptionOverviewInputController; +import info.novatec.inspectit.rcp.editor.table.input.HttpTimerDataInputController; +import info.novatec.inspectit.rcp.editor.table.input.InvocOverviewInputController; +import info.novatec.inspectit.rcp.editor.table.input.MethodInvocInputController; +import info.novatec.inspectit.rcp.editor.table.input.MultiInvocDataInputController; +import info.novatec.inspectit.rcp.editor.table.input.NavigationInvocOverviewInputController; +import info.novatec.inspectit.rcp.editor.table.input.SqlParameterAggregationInputControler; +import info.novatec.inspectit.rcp.editor.table.input.TaggedHttpTimerDataInputController; +import info.novatec.inspectit.rcp.editor.table.input.TimerDataInputController; +import info.novatec.inspectit.rcp.editor.table.input.UngroupedExceptionOverviewInputController; +import info.novatec.inspectit.rcp.editor.text.TextSubView; +import info.novatec.inspectit.rcp.editor.text.input.ClassesInputController; +import info.novatec.inspectit.rcp.editor.text.input.CpuInputController; +import info.novatec.inspectit.rcp.editor.text.input.MemoryInputController; +import info.novatec.inspectit.rcp.editor.text.input.SqlInvocSummaryTextInputController; +import info.novatec.inspectit.rcp.editor.text.input.SqlStatementTextInputController; +import info.novatec.inspectit.rcp.editor.text.input.ThreadsInputController; +import info.novatec.inspectit.rcp.editor.text.input.UngroupedExceptionOverviewStackTraceInputController; +import info.novatec.inspectit.rcp.editor.text.input.VmSummaryInputController; +import info.novatec.inspectit.rcp.editor.tree.SteppingTreeSubView; +import info.novatec.inspectit.rcp.editor.tree.TreeSubView; +import info.novatec.inspectit.rcp.editor.tree.input.ExceptionMessagesTreeInputController; +import info.novatec.inspectit.rcp.editor.tree.input.ExceptionTreeInputController; +import info.novatec.inspectit.rcp.editor.tree.input.SqlInputController; +import info.novatec.inspectit.rcp.editor.tree.input.SqlInvocInputController; +import info.novatec.inspectit.rcp.editor.tree.input.SteppingInvocDetailInputController; +import info.novatec.inspectit.rcp.model.SensorTypeEnum; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; + +/** + * The factory for the creation of a {@link ISubView}. + * + * @author Patrice Bouillet + * @author Eduard Tudenhoefner + * + */ +public final class SubViewFactory { + + /** + * Private constructor to prevent instantiation. + */ + private SubViewFactory() { + } + + /** + * Creates a default {@link ISubView} object based on the passed {@link SensorTypeEnum}. + * + * @param sensorTypeEnum + * The sensor type on which the default view controller is based on. + * @return An instance of a {@link ISubView}. + */ + public static ISubView createSubView(SensorTypeEnum sensorTypeEnum) { + switch (sensorTypeEnum) { + case AVERAGE_TIMER: + // same as Timer + case TIMER: + SashCompositeSubView timerSashSubView = new SashCompositeSubView(); + timerSashSubView.addSubView(new TableSubView(new TimerDataInputController())); + return timerSashSubView; + case CHARTING_TIMER: + GridCompositeSubView timerSubView = new GridCompositeSubView(); + timerSubView.addSubView(new GraphSubView(sensorTypeEnum), new GridData(SWT.FILL, SWT.FILL, true, true)); + ISubView aggregatedTimerSummarySubView = new TableSubView(new AggregatedTimerSummaryInputController()); + timerSubView.addSubView(aggregatedTimerSummarySubView, new GridData(SWT.FILL, SWT.FILL, true, false)); + return timerSubView; + case CHARTING_MULTI_TIMER: + return new GraphSubView(SensorTypeEnum.CHARTING_MULTI_TIMER); + case CLASSLOADING_INFORMATION: + GridCompositeSubView classLoadingSubView = new GridCompositeSubView(); + classLoadingSubView.addSubView(new GraphSubView(sensorTypeEnum), new GridData(SWT.FILL, SWT.FILL, true, true)); + classLoadingSubView.addSubView(new TextSubView(new ClassesInputController()), new GridData(SWT.FILL, SWT.FILL, true, false)); + return classLoadingSubView; + case MEMORY_INFORMATION: + GridCompositeSubView memorySubView = new GridCompositeSubView(); + memorySubView.addSubView(new GraphSubView(sensorTypeEnum), new GridData(SWT.FILL, SWT.FILL, true, true)); + memorySubView.addSubView(new TextSubView(new MemoryInputController()), new GridData(SWT.FILL, SWT.FILL, true, false)); + return memorySubView; + case CPU_INFORMATION: + GridCompositeSubView cpuSubView = new GridCompositeSubView(); + cpuSubView.addSubView(new GraphSubView(sensorTypeEnum), new GridData(SWT.FILL, SWT.FILL, true, true)); + cpuSubView.addSubView(new TextSubView(new CpuInputController()), new GridData(SWT.FILL, SWT.FILL, true, false)); + return cpuSubView; + case SYSTEM_INFORMATION: + return new TextSubView(new VmSummaryInputController()); + case THREAD_INFORMATION: + GridCompositeSubView threadSubView = new GridCompositeSubView(); + threadSubView.addSubView(new GraphSubView(sensorTypeEnum), new GridData(SWT.FILL, SWT.FILL, true, true)); + threadSubView.addSubView(new TextSubView(new ThreadsInputController()), new GridData(SWT.FILL, SWT.FILL, true, false)); + return threadSubView; + case INVOCATION_SEQUENCE: + GridCompositeSubView sqlCombinedView = new GridCompositeSubView(); + ISubView invocSql = new TreeSubView(new SqlInvocInputController()); + ISubView invocSqlSummary = new TextSubView(new SqlInvocSummaryTextInputController()); + sqlCombinedView.addSubView(invocSql, new GridData(SWT.FILL, SWT.FILL, true, true)); + sqlCombinedView.addSubView(invocSqlSummary, new GridData(SWT.FILL, SWT.FILL, true, false)); + + TabbedCompositeSubView invocTabbedSubView = new TabbedCompositeSubView(); + ISubView invocDetails = new SteppingTreeSubView(new SteppingInvocDetailInputController(false)); + ISubView invocMethods = new TableSubView(new MethodInvocInputController()); + ISubView invocExceptions = new TableSubView(new ExceptionSensorInvocInputController()); + invocTabbedSubView.addSubView(invocDetails, "Call Hierarchy", InspectIT.getDefault().getImage(InspectITImages.IMG_CALL_HIERARCHY)); + invocTabbedSubView.addSubView(sqlCombinedView, "SQL", InspectIT.getDefault().getImage(InspectITImages.IMG_DATABASE)); + invocTabbedSubView.addSubView(invocMethods, "Methods", InspectIT.getDefault().getImage(InspectITImages.IMG_METHOD_PUBLIC)); + invocTabbedSubView.addSubView(invocExceptions, "Exceptions", InspectIT.getDefault().getImage(InspectITImages.IMG_EXCEPTION_SENSOR)); + + SashCompositeSubView invocSubView = new SashCompositeSubView(); + ISubView invocOverview = new TableSubView(new InvocOverviewInputController()); + invocSubView.addSubView(invocOverview, 1); + invocSubView.addSubView(invocTabbedSubView, 2); + + return invocSubView; + case SQL: + SashCompositeSubView sqlSashSubView = new SashCompositeSubView(); + sqlSashSubView.addSubView(new TreeSubView(new SqlInputController()), 10); + sqlSashSubView.addSubView(new TableSubView(new SqlParameterAggregationInputControler()), 5); + sqlSashSubView.addSubView(new TextSubView(new SqlStatementTextInputController()), 1); + return sqlSashSubView; + case EXCEPTION_SENSOR: + SashCompositeSubView ungroupedExceptionSensorSubView = new SashCompositeSubView(); + ISubView ungroupedExceptionOverview = new TableSubView(new UngroupedExceptionOverviewInputController()); + TabbedCompositeSubView exceptionTreeTabbedSubView = new TabbedCompositeSubView(); + ISubView exceptionTree = new TreeSubView(new ExceptionTreeInputController()); + ISubView stackTraceInput = new TextSubView(new UngroupedExceptionOverviewStackTraceInputController()); + + exceptionTreeTabbedSubView.addSubView(exceptionTree, "Exception Tree", InspectIT.getDefault().getImage(InspectITImages.IMG_EXCEPTION_TREE)); + exceptionTreeTabbedSubView.addSubView(stackTraceInput, "Stack Trace", InspectIT.getDefault().getImage(InspectITImages.IMG_STACKTRACE)); + + ungroupedExceptionSensorSubView.addSubView(ungroupedExceptionOverview, 1); + ungroupedExceptionSensorSubView.addSubView(exceptionTreeTabbedSubView, 2); + return ungroupedExceptionSensorSubView; + case EXCEPTION_SENSOR_GROUPED: + SashCompositeSubView groupedExceptionSensorSubView = new SashCompositeSubView(); + ISubView groupedExceptionOverview = new TableSubView(new GroupedExceptionOverviewInputController()); + ISubView exceptionMessagesTree = new TreeSubView(new ExceptionMessagesTreeInputController()); + + groupedExceptionSensorSubView.addSubView(groupedExceptionOverview, 1); + groupedExceptionSensorSubView.addSubView(exceptionMessagesTree, 2); + return groupedExceptionSensorSubView; + case NAVIGATION_INVOCATION: + GridCompositeSubView sqlCombinedView1 = new GridCompositeSubView(); + ISubView invocSql1 = new TreeSubView(new SqlInvocInputController()); + ISubView invocSqlSummary1 = new TextSubView(new SqlInvocSummaryTextInputController()); + sqlCombinedView1.addSubView(invocSql1, new GridData(SWT.FILL, SWT.FILL, true, true)); + sqlCombinedView1.addSubView(invocSqlSummary1, new GridData(SWT.FILL, SWT.FILL, true, false)); + + TabbedCompositeSubView invocTabbedSubView1 = new TabbedCompositeSubView(); + ISubView invocDetails1 = new SteppingTreeSubView(new SteppingInvocDetailInputController(true)); + ISubView invocMethods1 = new TableSubView(new MethodInvocInputController()); + ISubView invocExceptions1 = new TableSubView(new ExceptionSensorInvocInputController()); + invocTabbedSubView1.addSubView(invocDetails1, "Call Hierarchy", InspectIT.getDefault().getImage(InspectITImages.IMG_CALL_HIERARCHY)); + invocTabbedSubView1.addSubView(sqlCombinedView1, "SQL", InspectIT.getDefault().getImage(InspectITImages.IMG_DATABASE)); + invocTabbedSubView1.addSubView(invocMethods1, "Methods", InspectIT.getDefault().getImage(InspectITImages.IMG_METHOD_PUBLIC)); + invocTabbedSubView1.addSubView(invocExceptions1, "Exceptions", InspectIT.getDefault().getImage(InspectITImages.IMG_EXCEPTION_SENSOR)); + + SashCompositeSubView invocSubView1 = new SashCompositeSubView(); + ISubView invocOverview1 = new TableSubView(new NavigationInvocOverviewInputController()); + invocSubView1.addSubView(invocOverview1, 1); + invocSubView1.addSubView(invocTabbedSubView1, 2); + + return invocSubView1; + case MULTI_INVOC_DATA: + SashCompositeSubView multiInvocSubView = new SashCompositeSubView(); + ISubView multiInvocOverview = new TableSubView(new MultiInvocDataInputController()); + TabbedCompositeSubView multiInvocTabbedSubView = new TabbedCompositeSubView(); + ISubView multiInvocSql = new TreeSubView(new SqlInvocInputController()); + ISubView multiInvocMethods = new TableSubView(new MethodInvocInputController()); + ISubView multiInvocExceptions = new TableSubView(new ExceptionSensorInvocInputController()); + + multiInvocTabbedSubView.addSubView(multiInvocSql, "SQL", InspectIT.getDefault().getImage(InspectITImages.IMG_DATABASE)); + multiInvocTabbedSubView.addSubView(multiInvocMethods, "Methods", InspectIT.getDefault().getImage(InspectITImages.IMG_METHOD_PUBLIC)); + multiInvocTabbedSubView.addSubView(multiInvocExceptions, "Exceptions", InspectIT.getDefault().getImage(InspectITImages.IMG_EXCEPTION_SENSOR)); + + multiInvocSubView.addSubView(multiInvocOverview, 1); + multiInvocSubView.addSubView(multiInvocTabbedSubView, 2); + + return multiInvocSubView; + case HTTP_TIMER_SENSOR: + SashCompositeSubView httpSashSubView = new SashCompositeSubView(); + httpSashSubView.addSubView(new TableSubView(new HttpTimerDataInputController())); + return httpSashSubView; + case TAGGED_HTTP_TIMER_SENSOR: + SashCompositeSubView taggedHttpSashSubView = new SashCompositeSubView(); + taggedHttpSashSubView.addSubView(new TableSubView(new TaggedHttpTimerDataInputController())); + return taggedHttpSashSubView; + case CHARTING_HTTP_TIMER_SENSOR: + return new GraphSubView(SensorTypeEnum.CHARTING_HTTP_TIMER_SENSOR); + default: + throw new IllegalArgumentException("Could not create sub-view. Not supported: " + sensorTypeEnum.toString()); + } + } + + /** + * Returns an instance of {@link ISubView}. + * + * @param fqn + * the fully-qualified name. + * @return An instance of {@link ISubView}. + */ + public static ISubView createSubView(String fqn) { + return createSubView(SensorTypeEnum.get(fqn)); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/composite/AbstractCompositeSubView.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/composite/AbstractCompositeSubView.java new file mode 100644 index 000000000..71a113bfe --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/composite/AbstractCompositeSubView.java @@ -0,0 +1,198 @@ +package info.novatec.inspectit.rcp.editor.composite; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.editor.AbstractSubView; +import info.novatec.inspectit.rcp.editor.ISubView; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jface.viewers.ISelectionProvider; + +/** + * Some general methods for composite views are implemented in here. + * + * @author Patrice Bouillet + * + */ +public abstract class AbstractCompositeSubView extends AbstractSubView { + + /** + * The list containing all the sub-views which are painted in this composite sub-view. + */ + private List subViews = new ArrayList(); + + /** + * Maximizes the given {@link ISubView}. The {@link ISubView} has to contained in this composite + * sub-view. + * + * @param subView + * Sub-view to maximize. + */ + public abstract void maximizeSubView(ISubView subView); + + /** + * Minimizes the given {@link ISubView}. The {@link ISubView} has to contained in this composite + * sub-view. + */ + public abstract void restoreMaximization(); + + /** + * Layouts the sub-views. + */ + public abstract void layout(); + + /** + * Adds a new sub-view to this composite view. + * + * @param subView + * The {@link ISubView} which will be added. + */ + public void addSubView(ISubView subView) { + subViews.add(subView); + } + + /** + * @return the subViews + */ + public List getSubViews() { + // makes the list unmodifiable so that it can not be edited. + return Collections.unmodifiableList(subViews); + } + + /** + * {@inheritDoc} + */ + @Override + public E getSubView(Class clazz) { + E view = super.getSubView(clazz); + if (null != view) { + return view; + } else { + for (ISubView subView : getSubViews()) { + view = subView.getSubView(clazz); + if (null != view) { + return view; + } + } + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public ISubView getSubViewWithInputController(Class inputControllerClass) { + for (ISubView subView : getSubViews()) { + ISubView view = subView.getSubViewWithInputController(inputControllerClass); + if (null != view) { + return view; + } + } + return null; + } + + /** + * {@inheritDoc} + *

+ * Delegates the select to all sub views. Implementing classes can override. + */ + @Override + public void select(ISubView subView) { + for (ISubView containedSubView : getSubViews()) { + containedSubView.select(subView); + } + } + + /** + * {@inheritDoc} + */ + public Set getPreferenceIds() { + Set preferenceIds = EnumSet.noneOf(PreferenceId.class); + + for (ISubView subView : subViews) { + preferenceIds.addAll(subView.getPreferenceIds()); + } + + return preferenceIds; + } + + /** + * {@inheritDoc} + */ + public void doRefresh() { + // just delegate to all sub-views. + for (ISubView subView : subViews) { + subView.doRefresh(); + } + } + + /** + * {@inheritDoc} + */ + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + // just delegate to all sub-views. + for (ISubView subView : subViews) { + subView.preferenceEventFired(preferenceEvent); + } + } + + /** + * {@inheritDoc} + */ + public void setDataInput(List data) { + // just delegate to all sub-views. + for (ISubView subView : subViews) { + subView.setDataInput(data); + } + layout(); + } + + /** + * {@inheritDoc} + */ + @Override + public void init() { + for (ISubView subView : subViews) { + subView.init(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setRootEditor(AbstractRootEditor rootEditor) { + super.setRootEditor(rootEditor); + + for (ISubView subView : subViews) { + subView.setRootEditor(rootEditor); + } + } + + /** + * {@inheritDoc} + */ + public ISelectionProvider getSelectionProvider() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + // just delegate to all sub-views. + for (ISubView subView : subViews) { + subView.dispose(); + } + } + +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/composite/GridCompositeSubView.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/composite/GridCompositeSubView.java new file mode 100644 index 000000000..728a38e9d --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/composite/GridCompositeSubView.java @@ -0,0 +1,168 @@ +package info.novatec.inspectit.rcp.editor.composite; + +import info.novatec.inspectit.rcp.editor.ISubView; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jface.viewers.IPostSelectionProvider; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * This implementation of a composite sub view uses the {@link GridLayout} to layout the associated + * children. + * + * @author Patrice Bouillet + * + */ +public class GridCompositeSubView extends AbstractCompositeSubView { + + /** + * The map contains the layout data objects. + */ + private Map layoutDataMap = new HashMap(); + + /** + * The composite of this sub-view. + */ + private Composite composite; + + /** + * The layout of the contained composite. + */ + private GridLayout gridLayout; + + /** + * Default constructor which calls + * {@link GridCompositeSubView#GridCompositeSubView(int, boolean)} with values 1 + * for the number of columns and false if the columns should have an equal width. + */ + public GridCompositeSubView() { + this(1, false); + } + + /** + * Constructor which constructs the {@link GridLayout} object with the passed values. + * + * @param numColumns + * The number of columns. + * @param makeColumnsEqualWidth + * If the columns should have an equal width. + * @see GridLayout + */ + public GridCompositeSubView(int numColumns, boolean makeColumnsEqualWidth) { + gridLayout = new GridLayout(numColumns, makeColumnsEqualWidth); + } + + /** + * Adds a new sub-view with the specified layout data to this composite view. + * + * @param subView + * The {@link ISubView} which will be added. + * @param layoutData + * The layout data of the corresponding sub-view. + */ + public void addSubView(ISubView subView, Object layoutData) { + super.addSubView(subView); + layoutDataMap.put(subView, layoutData); + } + + /** + * {@inheritDoc} + */ + public void createPartControl(Composite parent, FormToolkit toolkit) { + composite = toolkit.createComposite(parent); + composite.setLayout(gridLayout); + + for (final ISubView subView : getSubViews()) { + subView.createPartControl(composite, toolkit); + if (layoutDataMap.containsKey(subView)) { + subView.getControl().setLayoutData(layoutDataMap.get(subView)); + } + + subView.getControl().addFocusListener(new FocusAdapter() { + /** + * {@inheritDoc} + */ + @Override + public void focusGained(FocusEvent e) { + getRootEditor().setActiveSubView(subView); + } + }); + + if (null != subView.getSelectionProvider()) { + ISelectionProvider prov = subView.getSelectionProvider(); + prov.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + getRootEditor().setSelection(event.getSelection()); + } + }); + prov.addSelectionChangedListener(getRootEditor().getSelectionChangedListener()); + if (prov instanceof IPostSelectionProvider) { + ((IPostSelectionProvider) prov).addPostSelectionChangedListener(getRootEditor().getPostSelectionChangedListener()); + } + } + } + } + + /** + * {@inheritDoc} + */ + public Control getControl() { + return composite; + } + + /** + * {@inheritDoc} + */ + @Override + public void maximizeSubView(ISubView subView) { + ISubView maximizeSubView = subView; + if (maximizeSubView == null) { + maximizeSubView = getSubViews().get(0); + } + + for (ISubView view : getSubViews()) { + if (ObjectUtils.equals(view, maximizeSubView)) { + view.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + } else { + GridData gd = new GridData(); + gd.exclude = true; + view.getControl().setLayoutData(gd); + } + } + layout(); + } + + /** + * {@inheritDoc} + */ + @Override + public void restoreMaximization() { + for (ISubView view : getSubViews()) { + view.getControl().setLayoutData(layoutDataMap.get(view)); + } + layout(); + } + + /** + * {@inheritDoc} + */ + @Override + public void layout() { + composite.layout(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/composite/SashCompositeSubView.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/composite/SashCompositeSubView.java new file mode 100644 index 000000000..ec43fa0ab --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/composite/SashCompositeSubView.java @@ -0,0 +1,187 @@ +package info.novatec.inspectit.rcp.editor.composite; + +import info.novatec.inspectit.rcp.editor.ISubView; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jface.viewers.IPostSelectionProvider; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * The sash composite can create a composite sub-view which lays out its children either vertical or + * horizontal. The behavior can be set by passing the style {@link SWT#HORIZONTAL} or + * {@link SWT#VERTICAL} to the constructor. The default is {@link SWT#VERTICAL} + {@link SWT#SMOOTH} + * ; + * + * @author Patrice Bouillet + * + */ +public class SashCompositeSubView extends AbstractCompositeSubView { + + /** + * The style of the sash form. + */ + private int sashFormStyle = SWT.VERTICAL | SWT.SMOOTH; + + /** + * The generated composite of this sub-view. + */ + private SashForm sashForm; + + /** + * The weight mapping. + */ + private Map weightMapping = new HashMap(); + + /** + * Default constructor which takes no arguments. + */ + public SashCompositeSubView() { + } + + /** + * Additional constructor which can specify the style of the sash form. + *

+ *

+ * Styles: HORIZONTAL, VERTICAL, SMOOTH + *
+ *

+ * + * @param style + * The style of sash form to construct. + */ + public SashCompositeSubView(int style) { + this.sashFormStyle = style; + } + + /** + * {@inheritDoc} + */ + public void createPartControl(Composite parent, FormToolkit toolkit) { + sashForm = new SashForm(parent, sashFormStyle); + sashForm.setLayout(new GridLayout(1, false)); + + List subViews = getSubViews(); + + for (final ISubView subView : subViews) { + subView.createPartControl(sashForm, toolkit); + subView.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + subView.getControl().addFocusListener(new FocusAdapter() { + /** + * {@inheritDoc} + */ + @Override + public void focusGained(FocusEvent e) { + getRootEditor().setActiveSubView(subView); + } + }); + + if (null != subView.getSelectionProvider()) { + ISelectionProvider prov = subView.getSelectionProvider(); + prov.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + getRootEditor().setSelection(event.getSelection()); + } + }); + prov.addSelectionChangedListener(getRootEditor().getSelectionChangedListener()); + if (prov instanceof IPostSelectionProvider) { + ((IPostSelectionProvider) prov).addPostSelectionChangedListener(getRootEditor().getPostSelectionChangedListener()); + } + } + } + + if (!weightMapping.isEmpty()) { + int[] weights = new int[subViews.size()]; + for (int i = 0; i < subViews.size(); i++) { + if (weightMapping.containsKey(subViews.get(i))) { + weights[i] = weightMapping.get(subViews.get(i)); + } + } + sashForm.setWeights(weights); + } + } + + /** + * {@inheritDoc} + */ + public void addSubView(ISubView subView, int weight) { + super.addSubView(subView); + + weightMapping.put(subView, weight); + } + + /** + * {@inheritDoc} + */ + public Control getControl() { + return sashForm; + } + + /** + * {@inheritDoc} + */ + @Override + public void maximizeSubView(ISubView subView) { + ISubView maximizeSubView = subView; + if (maximizeSubView == null) { + maximizeSubView = getSubViews().get(0); + } + + int[] weights = new int[getSubViews().size()]; + int i = 0; + for (ISubView view : getSubViews()) { + if (ObjectUtils.equals(view, maximizeSubView)) { + view.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + weights[i] = 1; + } else { + GridData gd = new GridData(); + gd.exclude = true; + view.getControl().setLayoutData(gd); + weights[i] = 0; + } + i++; + } + sashForm.setWeights(weights); + layout(); + } + + /** + * {@inheritDoc} + */ + @Override + public void restoreMaximization() { + int[] weights = new int[getSubViews().size()]; + int i = 0; + for (ISubView view : getSubViews()) { + view.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + weights[i] = weightMapping.get(view); + i++; + } + sashForm.setWeights(weights); + layout(); + } + + /** + * {@inheritDoc} + */ + @Override + public void layout() { + sashForm.layout(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/composite/TabbedCompositeSubView.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/composite/TabbedCompositeSubView.java new file mode 100644 index 000000000..ebcdf18fb --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/composite/TabbedCompositeSubView.java @@ -0,0 +1,211 @@ +package info.novatec.inspectit.rcp.editor.composite; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.editor.ISubView; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.jface.viewers.IPostSelectionProvider; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CTabFolder; +import org.eclipse.swt.custom.CTabItem; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * This implementation of a composite view lays out its children in its own tabs. Every tab can be + * given a name. + * + * @author Patrice Bouillet + * + */ +public class TabbedCompositeSubView extends AbstractCompositeSubView { + + /** + * The container widget. + */ + private CTabFolder tabFolder; + + /** + * The names of the tabs. + */ + private Map tabNames = new HashMap(); + + /** + * The images of the tabs. + */ + private Map tabImageMap = new HashMap(); + + /** + * {@inheritDoc} + */ + public void createPartControl(Composite parent, FormToolkit toolkit) { + tabFolder = new CTabFolder(parent, SWT.BOTTOM | SWT.FLAT | SWT.H_SCROLL | SWT.V_SCROLL); + tabFolder.setBorderVisible(true); + + for (final ISubView subView : getSubViews()) { + subView.createPartControl(tabFolder, toolkit); + CTabItem item = new CTabItem(tabFolder, SWT.NONE, getPageCount()); + item.setControl(subView.getControl()); + item.setText(tabNames.get(subView)); + item.setImage(tabImageMap.get(subView)); + + subView.getControl().addFocusListener(new FocusAdapter() { + /** + * {@inheritDoc} + */ + @Override + public void focusGained(FocusEvent e) { + getRootEditor().setActiveSubView(subView); + } + }); + + if (null != subView.getSelectionProvider()) { + ISelectionProvider prov = subView.getSelectionProvider(); + prov.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + getRootEditor().setSelection(event.getSelection()); + } + }); + prov.addSelectionChangedListener(getRootEditor().getSelectionChangedListener()); + if (prov instanceof IPostSelectionProvider) { + ((IPostSelectionProvider) prov).addPostSelectionChangedListener(getRootEditor().getPostSelectionChangedListener()); + } + } + } + + tabFolder.setSelection(0); + } + + /** + * Returns the number of pages. + * + * @return the number of pages + */ + private int getPageCount() { + if (null != tabFolder && !tabFolder.isDisposed()) { + return tabFolder.getItemCount(); + } + return 0; + } + + /** + * Adds a sub-view to this tabbed view. + * + * @param subView + * The sub-view to add. + * @param tabName + * The name of the sub-view. + * @param tabImage + * The image of this sub-view. + */ + public void addSubView(ISubView subView, String tabName, Image tabImage) { + super.addSubView(subView); + + tabNames.put(subView, tabName); + tabImageMap.put(subView, tabImage); + } + + /** + * {@inheritDoc} + */ + @Override + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + super.preferenceEventFired(preferenceEvent); + fixTabControlsVisibility(); + } + + /** + * {@inheritDoc} + */ + public Control getControl() { + return tabFolder; + } + + /** + * {@inheritDoc} + */ + @Override + public void setDataInput(List data) { + super.setDataInput(data); + fixTabControlsVisibility(); + } + + /** + * {@inheritDoc} + */ + @Override + public void select(ISubView subView) { + int index = -1; + for (int i = 0; i < getSubViews().size(); i++) { + if (Objects.equals(subView, getSubViews().get(i))) { + index = i; + break; + } + } + + if (index > -1 && index < tabFolder.getItemCount()) { + tabFolder.setSelection(index); + } else { + super.select(subView); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void maximizeSubView(ISubView subView) { + // tabbed view already have maximized mode + // thus no maximization possible + } + + /** + * {@inheritDoc} + */ + @Override + public void restoreMaximization() { + // no minimization possible + } + + /** + * {@inheritDoc} + */ + @Override + public void layout() { + tabFolder.layout(); + + } + + /** + * In the Windows the visibility of the controls is mixed up if the controls that are in the + * tabs are accessed outside. This method fixes the visibility by setting the currently selected + * tab's control visible and all other not visible. + */ + private void fixTabControlsVisibility() { + // The following is needed for Bug INSPECTIT-184 + // The problem is that the visible attribute for windows seems not be + // correct under some circumstances. + CTabItem[] items = tabFolder.getItems(); + for (CTabItem cTabItem : items) { + if (cTabItem.equals(tabFolder.getSelection())) { + cTabItem.getControl().setVisible(true); + } else { + cTabItem.getControl().setVisible(false); + } + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/GraphSubView.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/GraphSubView.java new file mode 100644 index 000000000..c48a41f9c --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/GraphSubView.java @@ -0,0 +1,388 @@ +package info.novatec.inspectit.rcp.editor.graph; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.editor.AbstractSubView; +import info.novatec.inspectit.rcp.editor.ISubView; +import info.novatec.inspectit.rcp.editor.graph.plot.DateAxisZoomNotify; +import info.novatec.inspectit.rcp.editor.graph.plot.PlotController; +import info.novatec.inspectit.rcp.editor.graph.plot.ZoomListener; +import info.novatec.inspectit.rcp.editor.preferences.IPreferenceGroup; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId.LiveMode; +import info.novatec.inspectit.rcp.model.SensorTypeEnum; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; +import info.novatec.inspectit.rcp.repository.StorageRepositoryDefinition; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.label.type.impl.DataTimeFrameLabelType; +import info.novatec.inspectit.util.TimeFrame; + +import java.awt.Color; +import java.text.DateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.DateAxis; +import org.jfree.chart.axis.DateTickUnit; +import org.jfree.chart.axis.DateTickUnitType; +import org.jfree.chart.axis.TickUnits; +import org.jfree.chart.plot.CombinedDomainXYPlot; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.XYPlot; +import org.jfree.data.Range; +import org.jfree.experimental.chart.swt.ChartComposite; + +/** + * This sub-view can create charts which can contain themselves some plots. The plots are defined by + * {@link PlotController}. + * + * @author Patrice Bouillet + * + */ +public class GraphSubView extends AbstractSubView { + + /** + * The composite used to draw the items to. + */ + private Composite composite; + + /** + * The {@link JFreeChart} chart. + */ + private JFreeChart chart; + + /** + * The plot controller defines the visualized plots in the chart. + */ + private PlotController plotController; + + /** + * If we are in the auto update mode. + */ + private boolean autoUpdate = LiveMode.ACTIVE_DEFAULT; + + /** + * One minute in milliseconds. + */ + private static final long ONE_MINUTE = 60000L; + + /** + * Ten minutes in milliseconds. + */ + private static final long TEN_MINUTES = ONE_MINUTE * 10; + + /** + * The zoom listener. + */ + private ZoomListener zoomListener; + + /** + * Defines if a refresh job is currently already executing. + */ + private volatile boolean jobInSchedule = false; + + /** + * The constructor taking one parameter and creating a {@link PlotController}. + * + * @param fqn + * The fully-qualified-name of the corresponding sensor type. + */ + public GraphSubView(String fqn) { + this.plotController = PlotFactory.createDefaultPlotController(fqn); + } + + /** + * The constructor taking one parameter and creating a {@link PlotController}. + * + * @param sensorTypeEnum + * The sensor type enumeration of the corresponding sensor type. + */ + public GraphSubView(SensorTypeEnum sensorTypeEnum) { + this.plotController = PlotFactory.createDefaultPlotController(sensorTypeEnum); + } + + /** + * {@inheritDoc} + */ + @Override + public void init() { + plotController.setInputDefinition(getRootEditor().getInputDefinition()); + } + + /** + * {@inheritDoc} + */ + public void createPartControl(Composite parent, FormToolkit toolkit) { + // set the input definition + plotController.setRootEditor(getRootEditor()); + + // create the composite + composite = toolkit.createComposite(parent); + composite.setLayout(new FillLayout()); + + // create the chart + chart = createChart(); + if (!plotController.showLegend()) { + chart.removeLegend(); + } + Color color = new Color(toolkit.getColors().getBackground().getRed(), toolkit.getColors().getBackground().getGreen(), toolkit.getColors().getBackground().getBlue()); + chart.setBackgroundPaint(color); + + new ChartComposite(composite, SWT.NONE, chart, ChartComposite.DEFAULT_WIDTH, ChartComposite.DEFAULT_HEIGHT, 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE, true, true, true, true, true, true) { + + /** + * {@inheritDoc} + *

+ * On print we want to set the title so it's visible in the print page. + */ + @Override + public boolean print(GC gc) { + chart.setTitle(getRootEditor().getTitle()); + boolean res = super.print(gc); + chart.setTitle(""); + return res; + } + }; + } + + /** + * Creates and returns a {@link JFreeChart} chart. + * + * @return The {@link JFreeChart} chart. + */ + private JFreeChart createChart() { + DateAxisZoomNotify domainAxis = new DateAxisZoomNotify(); + domainAxis.setLowerMargin(0.0d); + domainAxis.setAutoRangeMinimumSize(100000.0d); + long now = System.currentTimeMillis(); + domainAxis.setRange(new Range(now - TEN_MINUTES, now + ONE_MINUTE), true, false); + + // set the ticks to display in the date axis + TickUnits source = new TickUnits(); + source.add(new DateTickUnit(DateTickUnitType.MINUTE, 1, DateFormat.getTimeInstance(DateFormat.SHORT))); + source.add(new DateTickUnit(DateTickUnitType.MINUTE, 5, DateFormat.getTimeInstance(DateFormat.SHORT))); + source.add(new DateTickUnit(DateTickUnitType.MINUTE, 30, DateFormat.getTimeInstance(DateFormat.SHORT))); + source.add(new DateTickUnit(DateTickUnitType.HOUR, 1, DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT))); + source.add(new DateTickUnit(DateTickUnitType.HOUR, 3, DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT))); + source.add(new DateTickUnit(DateTickUnitType.DAY, 1, DateFormat.getDateInstance(DateFormat.SHORT))); + source.add(new DateTickUnit(DateTickUnitType.DAY, 7, DateFormat.getDateInstance(DateFormat.MEDIUM))); + source.add(new DateTickUnit(DateTickUnitType.MONTH, 1, DateFormat.getDateInstance(DateFormat.MEDIUM))); + source.add(new DateTickUnit(DateTickUnitType.YEAR, 1, DateFormat.getDateInstance(DateFormat.MEDIUM))); + source.add(new DateTickUnit(DateTickUnitType.YEAR, 10, DateFormat.getDateInstance(DateFormat.MEDIUM))); + domainAxis.setStandardTickUnits(source); + domainAxis.setAutoTickUnitSelection(true); + + addZoomListener(domainAxis); + + CombinedDomainXYPlot plot = new CombinedDomainXYPlot(domainAxis); + plot.setGap(10.0); + + // add the subplots... + List subPlots = plotController.getPlots(); + for (XYPlot subPlot : subPlots) { + plot.add(subPlot, plotController.getWeight(subPlot)); + } + + plot.setOrientation(PlotOrientation.VERTICAL); + + TimeFrame timeFrame = getInitialDataTimeFrame(); + if (null != timeFrame) { + // set min/max dates + ((DateAxis) plot.getDomainAxis()).setMinimumDate(timeFrame.getFrom()); + ((DateAxis) plot.getDomainAxis()).setMaximumDate(timeFrame.getTo()); + + // inform the time-line widget via event + PreferenceEvent preferenceEvent = new PreferenceEvent(PreferenceId.TIMELINE); + Map map = new HashMap<>(); + map.put(PreferenceId.TimeLine.FROM_DATE_ID, timeFrame.getFrom()); + map.put(PreferenceId.TimeLine.TO_DATE_ID, timeFrame.getTo()); + preferenceEvent.setPreferenceMap(map); + getRootEditor().getPreferencePanel().fireEvent(preferenceEvent); + } + + // return a new chart containing the overlaid plot... + return new JFreeChart(plot); + } + + /** + * Adds the zoom listener to the domain axis. + * + * @param domainAxis + * The domain axis. + */ + private void addZoomListener(DateAxisZoomNotify domainAxis) { + if (null == zoomListener) { + zoomListener = new ZoomListener() { + public void zoomOccured() { + if (autoUpdate) { + autoUpdate = false; + getRootEditor().getPreferencePanel().disableLiveMode(); + } + doRefresh(); + } + }; + } + domainAxis.addZoomListener(zoomListener); + } + + /** + * Returns initial data time frame if one is defined. + * + * @return Returns initial data time frame if one is defined. + */ + private TimeFrame getInitialDataTimeFrame() { + RepositoryDefinition repositoryDefinition = getRootEditor().getInputDefinition().getRepositoryDefinition(); + if (repositoryDefinition instanceof StorageRepositoryDefinition) { + StorageRepositoryDefinition storageRepositoryDefinition = (StorageRepositoryDefinition) repositoryDefinition; + List> labels = storageRepositoryDefinition.getLocalStorageData().getLabels(new DataTimeFrameLabelType()); + if (CollectionUtils.isNotEmpty(labels)) { + return labels.get(0).getValue(); + } + } + return null; + } + + /** + * {@inheritDoc} + */ + public Control getControl() { + return composite; + } + + /** + * {@inheritDoc} + */ + public ISelectionProvider getSelectionProvider() { + return null; + } + + /** + * {@inheritDoc} + */ + public void setDataInput(List data) { + // nothing to do here + } + + /** + * {@inheritDoc} + */ + public Set getPreferenceIds() { + return plotController.getPreferenceIds(); + } + + /** + * {@inheritDoc} + */ + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + if (PreferenceId.TIMELINE.equals(preferenceEvent.getPreferenceId())) { + XYPlot plot = (XYPlot) chart.getPlot(); + DateAxis axis = (DateAxis) plot.getDomainAxis(); + + Map preferenceMap = preferenceEvent.getPreferenceMap(); + if (preferenceMap.containsKey(PreferenceId.TimeLine.TO_DATE_ID)) { + Date toDate = (Date) preferenceMap.get(PreferenceId.TimeLine.TO_DATE_ID); + axis.setMaximumDate(toDate); + } + if (preferenceMap.containsKey(PreferenceId.TimeLine.FROM_DATE_ID)) { + Date fromDate = (Date) preferenceMap.get(PreferenceId.TimeLine.FROM_DATE_ID); + axis.setMinimumDate(fromDate); + } + + doRefresh(); + } + + if (PreferenceId.LIVEMODE.equals(preferenceEvent.getPreferenceId())) { + Map preferenceMap = preferenceEvent.getPreferenceMap(); + if (preferenceMap.containsKey(PreferenceId.LiveMode.BUTTON_LIVE_ID)) { + autoUpdate = (Boolean) preferenceMap.get(PreferenceId.LiveMode.BUTTON_LIVE_ID); + if (autoUpdate) { + doRefresh(); + } + } + } + + plotController.preferenceEventFired(preferenceEvent); + } + + /** + * {@inheritDoc} + */ + public void doRefresh() { + if (checkDisposed()) { + return; + } + if (!jobInSchedule) { + jobInSchedule = true; + + XYPlot plot = (XYPlot) chart.getPlot(); + DateAxis axis = (DateAxis) plot.getDomainAxis(); + final Date minDate = axis.getMinimumDate(); + final Date maxDate = autoUpdate ? new Date(System.currentTimeMillis()) : axis.getMaximumDate(); + if (autoUpdate) { + axis.setMaximumDate(maxDate); + } + + Job job = new Job(getDataLoadingJobName()) { + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + plotController.update(minDate, maxDate); + return Status.OK_STATUS; + } catch (Throwable throwable) { // NOPMD + throw new RuntimeException("Unknown exception occurred trying to refresh the view.", throwable); + } finally { + jobInSchedule = false; + } + } + }; + job.schedule(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public ISubView getSubViewWithInputController(Class inputControllerClass) { + if (Objects.equals(inputControllerClass, plotController.getClass())) { + return this; + } + return null; + } + + /** + * Returns true if the composite holding the chart in the sub-view is disposed. False otherwise. + * + * @return Returns true if the composite holding the chart in the sub-view is disposed. False + * otherwise. + */ + private boolean checkDisposed() { + return composite.isDisposed(); + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + plotController.dispose(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/PlotFactory.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/PlotFactory.java new file mode 100644 index 000000000..d329afc4b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/PlotFactory.java @@ -0,0 +1,70 @@ +package info.novatec.inspectit.rcp.editor.graph; + +import info.novatec.inspectit.rcp.editor.graph.plot.DefaultClassesPlotController; +import info.novatec.inspectit.rcp.editor.graph.plot.DefaultCpuPlotController; +import info.novatec.inspectit.rcp.editor.graph.plot.DefaultMemoryPlotController; +import info.novatec.inspectit.rcp.editor.graph.plot.DefaultThreadsPlotController; +import info.novatec.inspectit.rcp.editor.graph.plot.HttpTimerPlotController; +import info.novatec.inspectit.rcp.editor.graph.plot.PlotController; +import info.novatec.inspectit.rcp.editor.graph.plot.TimerPlotController; +import info.novatec.inspectit.rcp.model.SensorTypeEnum; + +/** + * The factory for the plot creation. + * + * @author Patrice Bouillet + * + */ +public final class PlotFactory { + + /** + * The private constructor. + */ + private PlotFactory() { + } + + /** + * Creates and returns an instance of {@link PlotController}. + * + * @param sensorTypeEnum + * The {@link SensorTypeEnum}. + * @return An instance of {@link PlotController}. + */ + public static PlotController createDefaultPlotController(SensorTypeEnum sensorTypeEnum) { + switch (sensorTypeEnum) { + case CHARTING_TIMER: + case CHARTING_MULTI_TIMER: + return new TimerPlotController(); + case CLASSLOADING_INFORMATION: + return new DefaultClassesPlotController(); + case COMPILATION_INFORMATION: + return null; + case MEMORY_INFORMATION: + return new DefaultMemoryPlotController(); + case CPU_INFORMATION: + return new DefaultCpuPlotController(); + case RUNTIME_INFORMATION: + return null; + case SYSTEM_INFORMATION: + return null; + case THREAD_INFORMATION: + return new DefaultThreadsPlotController(); + case CHARTING_HTTP_TIMER_SENSOR: + return new HttpTimerPlotController(); + default: + return null; + } + } + + /** + * Returns an instance of {@link PlotController}. + * + * @param fqn + * The fully-qualified-name. + * @return An instance of {@link PlotController}. + */ + public static PlotController createDefaultPlotController(String fqn) { + return createDefaultPlotController(SensorTypeEnum.get(fqn)); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/AbstractPlotController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/AbstractPlotController.java new file mode 100644 index 000000000..24c4dd8e6 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/AbstractPlotController.java @@ -0,0 +1,168 @@ +package info.novatec.inspectit.rcp.editor.graph.plot; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.indexing.aggregation.IAggregator; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.IPreferenceGroup; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId.SamplingRate; +import info.novatec.inspectit.rcp.editor.preferences.control.SamplingRateControl; +import info.novatec.inspectit.rcp.editor.preferences.control.SamplingRateControl.Sensitivity; +import info.novatec.inspectit.rcp.editor.preferences.control.SamplingRateSelecterFactory; +import info.novatec.inspectit.rcp.editor.preferences.control.samplingrate.SamplingRateMode; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.Assert; + +/** + * The abstract class of the {@link PlotController} interface to provide some standard methods. + * + * @author Eduard Tudenhoefner + * + */ +public abstract class AbstractPlotController implements PlotController { + + /** + * The input definition. + */ + private InputDefinition inputDefinition; + + /** + * The root editor. + */ + private IRootEditor rootEditor; + + /** + * The sensitivity. + */ + private Sensitivity sensitivity = SamplingRateControl.DEFAULT_SENSITIVITY; + + /** + * The sampling rate mode identifier. + */ + private SamplingRateMode samplingRateMode = SamplingRateMode.TIMEFRAME_DIVIDER; + + /** + * {@inheritDoc} + */ + public void setInputDefinition(InputDefinition inputDefinition) { + Assert.isNotNull(inputDefinition); + + this.inputDefinition = inputDefinition; + } + + /** + * Returns the input definition. + * + * @return The input definition. + */ + protected InputDefinition getInputDefinition() { + Assert.isNotNull(inputDefinition); + + return inputDefinition; + } + + /** + * {@inheritDoc} + */ + public void setRootEditor(IRootEditor rootEditor) { + Assert.isNotNull(rootEditor); + + this.rootEditor = rootEditor; + } + + /** + * @return the rootEditor + */ + protected IRootEditor getRootEditor() { + Assert.isNotNull(rootEditor); + + return rootEditor; + } + + /** + * Adjusts the sampling rate. + * + * @param + * Type of element. + * @param from + * The start time. + * @param to + * The end time. + * @param dataObjects + * the data objects. + * @param aggregator + * {@link IAggregator} to be used. + * @return A {@link List} with the aggregated {@link DefaultData}. + */ + protected List adjustSamplingRate(List dataObjects, Date from, Date to, IAggregator aggregator) { + return samplingRateMode.adjustSamplingRate(dataObjects, from, to, sensitivity.getValue(), aggregator); + } + + /** + * {@inheritDoc} + */ + public void setSamplingRate(SamplingRateMode mode, Sensitivity sensitivity) { + this.samplingRateMode = mode; + this.sensitivity = sensitivity; + } + + /** + * Returns the sampling rate mode. + * + * @return The sampling rate mode. + */ + protected SamplingRateMode getSamplingRateMode() { + return samplingRateMode; + } + + /** + * Returns the sensitivity of the sampling rate mode. + * + * @return The sensitivity. + */ + protected Sensitivity getSensitivity() { + return sensitivity; + } + + /** + * {@inheritDoc} + */ + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + if (PreferenceId.SAMPLINGRATE.equals(preferenceEvent.getPreferenceId())) { + Map preferenceMap = preferenceEvent.getPreferenceMap(); + + // get the selected sampling rate mode + if (preferenceMap.containsKey(PreferenceId.SamplingRate.DIVIDER_ID)) { + SamplingRate samplingRateIdEnum = (SamplingRate) preferenceMap.get(PreferenceId.SamplingRate.DIVIDER_ID); + samplingRateMode = SamplingRateSelecterFactory.selectSamplingRateMode(samplingRateIdEnum); + } + + if (preferenceMap.containsKey(PreferenceId.SamplingRate.SLIDER_ID)) { + sensitivity = (Sensitivity) preferenceMap.get(PreferenceId.SamplingRate.SLIDER_ID); + } + } + + } + + /** + * {@inheritDoc} + *

+ * By default legend is not shown. + */ + public boolean showLegend() { + return false; + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/AbstractTimerDataPlotController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/AbstractTimerDataPlotController.java new file mode 100644 index 000000000..5196b07ac --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/AbstractTimerDataPlotController.java @@ -0,0 +1,267 @@ +package info.novatec.inspectit.rcp.editor.graph.plot; + +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.geom.Ellipse2D; +import java.text.DateFormat; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.widgets.Display; +import org.jfree.chart.axis.AxisLocation; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.labels.StandardXYToolTipGenerator; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.DeviationRenderer; +import org.jfree.chart.renderer.xy.XYBarRenderer; +import org.jfree.data.RangeType; +import org.jfree.data.time.Millisecond; +import org.jfree.data.time.TimeSeries; +import org.jfree.data.time.TimeSeriesCollection; +import org.jfree.data.xy.XYBarDataset; +import org.jfree.data.xy.YIntervalSeriesCollection; +import org.jfree.ui.RectangleInsets; + +/** + * Abstract plot controller for all graphs concerning the timer data and it's sub-classes. + * + * @author Ivan Senic + * + * @param + * Type of template + */ +public abstract class AbstractTimerDataPlotController extends AbstractPlotController { + + /** + * Colors we will use for series. + */ + private static final int[] SERIES_COLORS = new int[] { SWT.COLOR_RED, SWT.COLOR_BLUE, SWT.COLOR_DARK_GREEN, SWT.COLOR_DARK_YELLOW, SWT.COLOR_DARK_GRAY, SWT.COLOR_BLACK, SWT.COLOR_DARK_CYAN, + SWT.COLOR_DARK_BLUE }; + + /** + * The map containing the weight of the {@link XYPlot}s. + */ + private Map weights = new HashMap(); + + /** + * Plot used to display duration of the HTTP requests. + */ + private XYPlot durationPlot; + + /** + * Plot used to display the count of the HTTP requests. + */ + private XYPlot countPlot; + + /** + * Duration series. + */ + private List durationSeries; + + /** + * Count series. + */ + private List countSeries; + + /** + * Returns series key for given template. + * + * @param template + * Template + * @return {@link Comparable} representing series key. + */ + protected abstract Comparable getSeriesKey(E template); + + /** + * Returns the template list. Each object in this list will represent one series. + * + * @return Returns the template list. + */ + protected abstract List getTemplates(); + + /** + * {@inheritDoc} + */ + @Override + public Set getPreferenceIds() { + Set preferenceIds = EnumSet.noneOf(PreferenceId.class); + if (getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) { + preferenceIds.add(PreferenceId.LIVEMODE); + } + preferenceIds.add(PreferenceId.TIMELINE); + preferenceIds.add(PreferenceId.SAMPLINGRATE); + preferenceIds.add(PreferenceId.UPDATE); + return preferenceIds; + } + + /** + * {@inheritDoc} + */ + @Override + public List getPlots() { + durationPlot = initializeDurationPlot(); + countPlot = initializeCountPlot(); + + weights.put(durationPlot, 2); + weights.put(countPlot, 1); + + List list = new ArrayList(); + Collections.addAll(list, durationPlot, countPlot); + return list; + } + + /** + * {@inheritDoc} + */ + @Override + public int getWeight(XYPlot subPlot) { + return weights.get(subPlot); + } + + /** + * Removes all data from the upper plot and sets the {@link TimerData} objects on the plot. + * + * @param map + * The data to set on the plot. + */ + protected void setDurationPlotData(Map> map) { + for (YIntervalSeriesImproved series : durationSeries) { + series.clear(); + for (Entry> entry : map.entrySet()) { + if (series.getKey().equals(entry.getKey())) { + for (E data : entry.getValue()) { + series.add(data.getTimeStamp().getTime(), data.getAverage(), data.getMin(), data.getMax(), false); + } + break; + } + } + series.fireSeriesChanged(); + } + } + + /** + * Removes all data from the upper plot and sets the {@link TimerData} objects on the plot. + * + * @param map + * The data to set on the plot. + */ + protected void setCountPlotData(Map> map) { + for (TimeSeries series : countSeries) { + series.clear(); + series.setNotify(false); + for (Entry> entry : map.entrySet()) { + if (series.getKey().equals(entry.getKey())) { + for (E data : entry.getValue()) { + series.addOrUpdate(new Millisecond(data.getTimeStamp()), data.getCount()); + } + break; + } + } + series.setNotify(true); + series.fireSeriesChanged(); + } + } + + /** + * Initializes the duration plot. + * + * @return An instance of {@link XYPlot}. + */ + private XYPlot initializeDurationPlot() { + Set> keys = new HashSet<>(); + durationSeries = new ArrayList(); + YIntervalSeriesCollection yintervalseriescollection = new YIntervalSeriesCollection(); + for (E template : getTemplates()) { + Comparable seriesKey = getSeriesKey(template); + if (keys.add(seriesKey)) { + YIntervalSeriesImproved yIntervalSeries = new YIntervalSeriesImproved(seriesKey); + yintervalseriescollection.addSeries(yIntervalSeries); + durationSeries.add(yIntervalSeries); + } + } + + DeviationRenderer renderer = new DeviationRenderer(true, false); + renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT, DateFormat.getDateTimeInstance(), NumberFormat.getNumberInstance())); + renderer.setBaseShapesVisible(true); + renderer.setAlpha(0.1f); + Display display = Display.getDefault(); + for (int i = 0; i < durationSeries.size(); i++) { + int color = SERIES_COLORS[i % SERIES_COLORS.length]; + RGB rgb = display.getSystemColor(color).getRGB(); + renderer.setSeriesStroke(i, new BasicStroke(3.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + renderer.setSeriesFillPaint(i, new Color(rgb.red, rgb.green, rgb.blue)); + renderer.setSeriesOutlineStroke(i, new BasicStroke(2.0f)); + renderer.setSeriesShape(i, new Ellipse2D.Double(-2.5, -2.5, 5.0, 5.0)); + } + NumberAxis rangeAxis = new NumberAxis("ms"); + rangeAxis.setAutoRangeMinimumSize(100.0d); + rangeAxis.setRangeType(RangeType.POSITIVE); + rangeAxis.setAutoRangeIncludesZero(true); + + XYPlot subplot = new XYPlot(yintervalseriescollection, null, rangeAxis, renderer); + subplot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0)); + subplot.setRangeAxisLocation(AxisLocation.TOP_OR_LEFT, false); + subplot.setRangeCrosshairVisible(true); + + return subplot; + } + + /** + * Initializes the lower plot. + * + * @return An instance of {@link XYPlot} + */ + private XYPlot initializeCountPlot() { + Set> keys = new HashSet<>(); + countSeries = new ArrayList(); + TimeSeriesCollection dataset = new TimeSeriesCollection(); + for (E template : getTemplates()) { + Comparable seriesKey = getSeriesKey(template); + if (keys.add(seriesKey)) { + TimeSeries timeSeries = new TimeSeries(seriesKey); + countSeries.add(timeSeries); + dataset.addSeries(timeSeries); + } + } + + // ISE: No idea why we have 30 here, used same value as in other charts + XYBarDataset ds = new XYBarDataset(dataset, 30); + + XYBarRenderer renderer = new XYBarRenderer(); + renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT, DateFormat.getDateTimeInstance(), NumberFormat.getNumberInstance())); + renderer.setShadowVisible(false); + renderer.setMargin(0.1d); + Display display = Display.getDefault(); + for (int i = 0; i < countSeries.size(); i++) { + int color = SERIES_COLORS[i % SERIES_COLORS.length]; + RGB rgb = display.getSystemColor(color).getRGB(); + renderer.setSeriesPaint(i, new Color(rgb.red, rgb.green, rgb.blue)); + renderer.setSeriesVisibleInLegend(i, Boolean.FALSE); + } + + NumberAxis rangeAxis = new NumberAxis("Count"); + rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); + rangeAxis.setAutoRange(true); + rangeAxis.setRangeType(RangeType.POSITIVE); + + XYPlot subplot = new XYPlot(ds, null, rangeAxis, renderer); + subplot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0)); + subplot.setRangeAxisLocation(AxisLocation.TOP_OR_LEFT); + + return subplot; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DateAxisZoomNotify.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DateAxisZoomNotify.java new file mode 100644 index 000000000..e5d879de5 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DateAxisZoomNotify.java @@ -0,0 +1,75 @@ +package info.novatec.inspectit.rcp.editor.graph.plot; + +import org.eclipse.core.runtime.ListenerList; +import org.jfree.chart.axis.DateAxis; + +/** + * This class extends the date axis from JFreeChart and adds the possibility to be notified if a + * zooming event occurs. + * + * @author Patrice Bouillet + * + */ +public class DateAxisZoomNotify extends DateAxis { + + /** + * Generated UID. + */ + private static final long serialVersionUID = 2365282634625595024L; + + /** + * The registered listeners. + */ + private transient ListenerList zoomListeners = new ListenerList(); + + /** + * Adds a zoom listener. + * + * @param zoomListener + * The zoom listener to add. + */ + public void addZoomListener(ZoomListener zoomListener) { + zoomListeners.add(zoomListener); + } + + /** + * Removes a zoom listener. + * + * @param zoomListener + * The zoom listener to remove. + */ + public void removeZoomListener(ZoomListener zoomListener) { + zoomListeners.remove(zoomListener); + } + + /** + * Notifies all zoom listeners that a zooming event occurred. + */ + public void notifyZoomListeners() { + Object[] listeners = zoomListeners.getListeners(); + for (int i = 0; i < listeners.length; ++i) { + ((ZoomListener) listeners[i]).zoomOccured(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void resizeRange(double percent) { + super.resizeRange(percent); + + notifyZoomListeners(); + } + + /** + * {@inheritDoc} + */ + @Override + public void zoomRange(double lowerPercent, double upperPercent) { + super.zoomRange(lowerPercent, upperPercent); + + notifyZoomListeners(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DefaultClassesPlotController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DefaultClassesPlotController.java new file mode 100644 index 000000000..7312fa8a0 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DefaultClassesPlotController.java @@ -0,0 +1,334 @@ +package info.novatec.inspectit.rcp.editor.graph.plot; + +import info.novatec.inspectit.cmr.service.IGlobalDataAccessService; +import info.novatec.inspectit.communication.data.ClassLoadingInformationData; +import info.novatec.inspectit.indexing.aggregation.IAggregator; +import info.novatec.inspectit.indexing.aggregation.impl.ClassLoadingInformationDataAggregator; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.geom.Ellipse2D; +import java.text.DateFormat; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.swt.widgets.Display; +import org.jfree.chart.axis.AxisLocation; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.labels.StandardXYToolTipGenerator; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.DeviationRenderer; +import org.jfree.data.RangeType; +import org.jfree.data.xy.YIntervalSeriesCollection; +import org.jfree.ui.RectangleInsets; + +/** + * This class creates a {@link XYPlot} containing the {@link ClassLoadingInformationData} + * informations. + * + * @author Eduard Tudenhoefner + * @author Patrice Bouillet + * + */ +public class DefaultClassesPlotController extends AbstractPlotController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.graph.classes"; + + /** + * The template of the {@link ClassLoadingInformationData} object. + */ + private ClassLoadingInformationData template; + + /** + * Indicates the weight of the upper {@link XYPlot}. + */ + private static final int WEIGHT_UPPER_PLOT = 1; + + /** + * The upper {@link XYPlot} containing the graphical view. + */ + private XYPlot upperPlot; + + /** + * The map containing the weights of the {@link XYPlot}s. + */ + private Map weights = new HashMap(); + + /** + * The {@link YIntervalSeriesImproved}. + */ + private YIntervalSeriesImproved loadedClasses; + + /** + * The {@link YIntervalSeriesImproved}. + */ + private YIntervalSeriesImproved totalLoadedClasses; + + /** + * The data access service to access the data on the CMR. + */ + private IGlobalDataAccessService dataAccessService; + + /** + * Old list containing some data objects which could be reused. + */ + private List oldData = Collections.emptyList(); + + /** + * The old from date. + */ + private Date oldFromDate = new Date(Long.MAX_VALUE); + + /** + * The old to date. + */ + private Date oldToDate = new Date(0); + + /** + * This represents the date of one of the objects which was received at some time in the past + * but was the one with the newest date. This is needed for not requesting some data of the CMR + * sometimes. + */ + private Date newestDate = new Date(0); + + /** + * {@link IAggregator}. + */ + private IAggregator aggregator; + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + template = new ClassLoadingInformationData(); + template.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); + template.setSensorTypeIdent(inputDefinition.getIdDefinition().getSensorTypeId()); + template.setId(-1L); + + dataAccessService = inputDefinition.getRepositoryDefinition().getGlobalDataAccessService(); + aggregator = new ClassLoadingInformationDataAggregator(); + } + + /** + * {@inheritDoc} + */ + public List getPlots() { + upperPlot = initializeUpperPlot(); + + List plots = new ArrayList(1); + plots.add(upperPlot); + weights.put(upperPlot, WEIGHT_UPPER_PLOT); + + return plots; + } + + /** + * Initializes the upper plot. + * + * @return An instance of {@link XYPlot}. + */ + private XYPlot initializeUpperPlot() { + loadedClasses = new YIntervalSeriesImproved("loaded classes"); + totalLoadedClasses = new YIntervalSeriesImproved("total loaded classes"); + + YIntervalSeriesCollection yintervalseriescollection = new YIntervalSeriesCollection(); + yintervalseriescollection.addSeries(loadedClasses); + yintervalseriescollection.addSeries(totalLoadedClasses); + + DeviationRenderer renderer = new DeviationRenderer(true, false); + renderer.setBaseShapesVisible(true); + renderer.setSeriesStroke(0, new BasicStroke(3.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + renderer.setSeriesFillPaint(0, new Color(255, 200, 200)); + renderer.setSeriesOutlineStroke(0, new BasicStroke(2.0f)); + renderer.setSeriesShape(0, new Ellipse2D.Double(-2.5, -2.5, 5.0, 5.0)); + renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT, DateFormat.getDateTimeInstance(), NumberFormat.getNumberInstance())); + + final NumberAxis rangeAxis = new NumberAxis("Classes"); + rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); + rangeAxis.setAutoRangeMinimumSize(2000.0d); + rangeAxis.setRangeType(RangeType.POSITIVE); + rangeAxis.setAutoRangeIncludesZero(true); + + final XYPlot subplot = new XYPlot(yintervalseriescollection, null, rangeAxis, renderer); + subplot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0)); + subplot.setRangeAxisLocation(AxisLocation.TOP_OR_LEFT); + subplot.setRangeCrosshairVisible(true); + + return subplot; + } + + /** + * Updates the upper plot with the given input data. + * + * @param classLoadingData + * The input data. + */ + private void addUpperPlotData(List classLoadingData) { + for (ClassLoadingInformationData data : classLoadingData) { + int loadedClassAverage = data.getTotalLoadedClassCount() / data.getCount(); + long totalLoadedClassAverage = data.getTotalTotalLoadedClassCount() / data.getCount(); + loadedClasses.add(data.getTimeStamp().getTime(), loadedClassAverage, data.getMinLoadedClassCount(), data.getMaxLoadedClassCount(), false); + totalLoadedClasses.add(data.getTimeStamp().getTime(), totalLoadedClassAverage, data.getMinTotalLoadedClassCount(), data.getMaxTotalLoadedClassCount(), false); + } + loadedClasses.fireSeriesChanged(); + totalLoadedClasses.fireSeriesChanged(); + } + + /** + * Removes all data from the upper plot and sets the {@link ClassLoadingInformationData} objects + * on the plot. + * + * @param classLoadingData + * The data to set on the plot. + */ + private void setUpperPlotData(List classLoadingData) { + loadedClasses.clear(); + totalLoadedClasses.clear(); + addUpperPlotData(classLoadingData); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public void update(final Date from, final Date to) { + Date dataNewestDate = new Date(0); + if (!oldData.isEmpty()) { + dataNewestDate = oldData.get(oldData.size() - 1).getTimeStamp(); + } + boolean leftAppend = from.before(oldFromDate); + // boolean rightAppend = to.after(dataNewestDate) && + // (to.equals(newestDate) || to.after(newestDate)); + boolean rightAppend = to.after(newestDate) || oldToDate.before(to); + + List adjustedClassLoadingData = Collections.emptyList(); + + if (oldData.isEmpty() || to.before(oldFromDate) || from.after(dataNewestDate)) { + // the old data is empty or the range does not fit, thus we need + // to access the whole range + List data = (List) dataAccessService.getDataObjectsFromToDate(template, from, to); + + if (!data.isEmpty()) { + adjustedClassLoadingData = adjustSamplingRate(data, from, to, aggregator); + + // we got some data, thus we can set the date + oldFromDate = (Date) from.clone(); + oldToDate = (Date) to.clone(); + if (newestDate.before(data.get(data.size() - 1).getTimeStamp())) { + newestDate = new Date(data.get(data.size() - 1).getTimeStamp().getTime()); + } + } + oldData = data; + } else if (leftAppend && rightAppend) { + // we have some data in between, but we need to append something + // to the start and to the end + Date rightDate = new Date(newestDate.getTime() + 1); + Date leftDate = new Date(oldFromDate.getTime() - 1); + + List rightData = (List) dataAccessService.getDataObjectsFromToDate(template, rightDate, to); + List leftData = (List) dataAccessService.getDataObjectsFromToDate(template, from, leftDate); + + if (!leftData.isEmpty()) { + oldData.addAll(0, leftData); + oldFromDate = (Date) from.clone(); + } + + if (!rightData.isEmpty()) { + oldData.addAll(rightData); + oldToDate = (Date) to.clone(); + if (newestDate.before(rightData.get(rightData.size() - 1).getTimeStamp())) { + newestDate = new Date(rightData.get(rightData.size() - 1).getTimeStamp().getTime()); + } + } + + adjustedClassLoadingData = adjustSamplingRate(oldData, from, to, aggregator); + } else if (rightAppend) { + // just append something on the right + Date rightDate = new Date(newestDate.getTime() + 1); + + List timerData = (List) dataAccessService.getDataObjectsFromToDate(template, rightDate, to); + + if (!timerData.isEmpty()) { + oldData.addAll(timerData); + oldToDate = (Date) to.clone(); + if (newestDate.before(timerData.get(timerData.size() - 1).getTimeStamp())) { + newestDate = new Date(timerData.get(timerData.size() - 1).getTimeStamp().getTime()); + } + } + + adjustedClassLoadingData = adjustSamplingRate(oldData, from, to, aggregator); + } else if (leftAppend) { + // just append something on the left + Date leftDate = new Date(oldFromDate.getTime() - 1); + + List timerData = (List) dataAccessService.getDataObjectsFromToDate(template, from, leftDate); + + if (!timerData.isEmpty()) { + oldData.addAll(timerData); + oldFromDate = (Date) from.clone(); + } + + adjustedClassLoadingData = adjustSamplingRate(oldData, from, to, aggregator); + } else { + // No update is needed here because we already have all the + // needed data + adjustedClassLoadingData = adjustSamplingRate(oldData, from, to, aggregator); + } + + final List finalAdjustedClassLoadingData = adjustedClassLoadingData; + + // updating the plots in the UI thread + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + setUpperPlotData(finalAdjustedClassLoadingData); + } + }); + + } + + /** + * {@inheritDoc} + */ + public int getWeight(XYPlot subPlot) { + return weights.get(subPlot); + } + + /** + * {@inheritDoc} + */ + public Set getPreferenceIds() { + Set preferenceIds = EnumSet.noneOf(PreferenceId.class); + if (getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) { + preferenceIds.add(PreferenceId.LIVEMODE); + } + preferenceIds.add(PreferenceId.TIMELINE); + preferenceIds.add(PreferenceId.SAMPLINGRATE); + preferenceIds.add(PreferenceId.UPDATE); + return preferenceIds; + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DefaultCpuPlotController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DefaultCpuPlotController.java new file mode 100644 index 000000000..e0f77e4e2 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DefaultCpuPlotController.java @@ -0,0 +1,323 @@ +package info.novatec.inspectit.rcp.editor.graph.plot; + +import info.novatec.inspectit.cmr.service.IGlobalDataAccessService; +import info.novatec.inspectit.communication.data.CpuInformationData; +import info.novatec.inspectit.indexing.aggregation.IAggregator; +import info.novatec.inspectit.indexing.aggregation.impl.CpuInformationDataAggregator; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.geom.Ellipse2D; +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.swt.widgets.Display; +import org.jfree.chart.axis.AxisLocation; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.axis.NumberTickUnit; +import org.jfree.chart.labels.StandardXYToolTipGenerator; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.DeviationRenderer; +import org.jfree.data.Range; +import org.jfree.data.RangeType; +import org.jfree.data.xy.YIntervalSeriesCollection; +import org.jfree.ui.RectangleInsets; + +/** + * This class creates a {@link XYPlot} containing the {@link CpuInformationData} informations. + * + * @author Eduard Tudenhoefner + * @author Patrice Bouillet + * + */ +public class DefaultCpuPlotController extends AbstractPlotController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.graph.cpu"; + + /** + * The template of the {@link CpuInformationData} object. + */ + private CpuInformationData template; + + /** + * Indicates the weight of the upper {@link XYPlot}. + */ + private static final int WEIGHT_UPPER_PLOT = 2; + + /** + * The upper {@link XYPlot} containing the graphical view. + */ + private XYPlot upperPlot; + + /** + * The map containing the weights of the {@link XYPlot}s. + */ + private Map weights = new HashMap(); + + /** + * The {@link YIntervalSeriesImproved}. + */ + private YIntervalSeriesImproved cpuUsage; + + /** + * The data access service to access the data on the CMR. + */ + private IGlobalDataAccessService dataAccessService; + + /** + * Old list containing some data objects which could be reused. + */ + private List oldData = Collections.emptyList(); + + /** + * The old from date. + */ + private Date oldFromDate = new Date(Long.MAX_VALUE); + + /** + * The old to date. + */ + private Date oldToDate = new Date(0); + + /** + * This represents the date of one of the objects which was received at some time in the past + * but was the one with the newest date. This is needed for not requesting some data of the CMR + * sometimes. + */ + private Date newestDate = new Date(0); + + /** + * {@link IAggregator}. + */ + private IAggregator aggregator = new CpuInformationDataAggregator(); + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + template = new CpuInformationData(); + template.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); + template.setSensorTypeIdent(inputDefinition.getIdDefinition().getSensorTypeId()); + template.setId(-1L); + + dataAccessService = inputDefinition.getRepositoryDefinition().getGlobalDataAccessService(); + } + + /** + * {@inheritDoc} + */ + public List getPlots() { + upperPlot = initializeUpperPlot(); + + List plots = new ArrayList(1); + plots.add(upperPlot); + weights.put(upperPlot, WEIGHT_UPPER_PLOT); + + return plots; + } + + /** + * Initializes the upper plot. + * + * @return An instance of {@link XYPlot}. + */ + private XYPlot initializeUpperPlot() { + cpuUsage = new YIntervalSeriesImproved("cpu usage"); + + YIntervalSeriesCollection yintervalseriescollection = new YIntervalSeriesCollection(); + yintervalseriescollection.addSeries(cpuUsage); + + DeviationRenderer renderer = new DeviationRenderer(true, false); + renderer.setBaseShapesVisible(true); + renderer.setSeriesStroke(0, new BasicStroke(3.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + renderer.setSeriesFillPaint(0, new Color(255, 200, 200)); + renderer.setSeriesOutlineStroke(0, new BasicStroke(2.0f)); + renderer.setSeriesShape(0, new Ellipse2D.Double(-2.5, -2.5, 5.0, 5.0)); + renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT, DateFormat.getDateTimeInstance(), NumberFormat.getNumberInstance())); + + final NumberAxis rangeAxis = new NumberAxis("CPU usage of the VM"); + rangeAxis.setRange(new Range(0, 100), true, false); + rangeAxis.setAutoRangeMinimumSize(100.0d, false); + rangeAxis.setTickUnit(new NumberTickUnit(10.0d, new DecimalFormat("0"))); + rangeAxis.setRangeType(RangeType.POSITIVE); + + final XYPlot subplot = new XYPlot(yintervalseriescollection, null, rangeAxis, renderer); + subplot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0)); + subplot.setRangeAxisLocation(AxisLocation.TOP_OR_LEFT); + subplot.setRangeCrosshairVisible(true); + + return subplot; + } + + /** + * Updates the upper plot with the given input data. + * + * @param cpuData + * The input data. + */ + private void addUpperPlotData(List cpuData) { + for (CpuInformationData data : cpuData) { + float cpuAverage = data.getTotalCpuUsage() / data.getCount(); + cpuUsage.add(data.getTimeStamp().getTime(), cpuAverage, data.getMinCpuUsage(), data.getMaxCpuUsage(), false); + } + cpuUsage.fireSeriesChanged(); + } + + /** + * Removes all data from the upper plot and sets the {@link CpuInformationData} objects on the + * plot. + * + * @param cpuInformationData + * The data to set on the plot. + */ + private void setUpperPlotData(List cpuInformationData) { + cpuUsage.clear(); + addUpperPlotData(cpuInformationData); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public void update(Date from, Date to) { + Date dataNewestDate = new Date(0); + if (!oldData.isEmpty()) { + dataNewestDate = oldData.get(oldData.size() - 1).getTimeStamp(); + } + boolean leftAppend = from.before(oldFromDate); + // boolean rightAppend = to.after(dataNewestDate) && + // (to.equals(newestDate) || to.after(newestDate)); + boolean rightAppend = to.after(newestDate) || oldToDate.before(to); + + List adjustedCpuData = Collections.emptyList(); + + if (oldData.isEmpty() || to.before(oldFromDate) || from.after(dataNewestDate)) { + // the old data is empty or the range does not fit, thus we need + // to access the whole range + List data = (List) dataAccessService.getDataObjectsFromToDate(template, from, to); + + if (!data.isEmpty()) { + adjustedCpuData = adjustSamplingRate(data, from, to, aggregator); + + // we got some data, thus we can set the date + oldFromDate = (Date) from.clone(); + oldToDate = (Date) to.clone(); + if (newestDate.before(data.get(data.size() - 1).getTimeStamp())) { + newestDate = new Date(data.get(data.size() - 1).getTimeStamp().getTime()); + } + } + oldData = data; + } else if (leftAppend && rightAppend) { + // we have some data in between, but we need to append something + // to the start and to the end + Date rightDate = new Date(newestDate.getTime() + 1); + Date leftDate = new Date(oldFromDate.getTime() - 1); + + List rightData = (List) dataAccessService.getDataObjectsFromToDate(template, rightDate, to); + List leftData = (List) dataAccessService.getDataObjectsFromToDate(template, from, leftDate); + + if (!leftData.isEmpty()) { + oldData.addAll(0, leftData); + oldFromDate = (Date) from.clone(); + } + + if (!rightData.isEmpty()) { + oldData.addAll(rightData); + oldToDate = (Date) to.clone(); + if (newestDate.before(rightData.get(rightData.size() - 1).getTimeStamp())) { + newestDate = new Date(rightData.get(rightData.size() - 1).getTimeStamp().getTime()); + } + } + + adjustedCpuData = adjustSamplingRate(oldData, from, to, aggregator); + } else if (rightAppend) { + // just append something on the right + Date rightDate = new Date(newestDate.getTime() + 1); + + List timerData = (List) dataAccessService.getDataObjectsFromToDate(template, rightDate, to); + + if (!timerData.isEmpty()) { + oldData.addAll(timerData); + oldToDate = (Date) to.clone(); + if (newestDate.before(timerData.get(timerData.size() - 1).getTimeStamp())) { + newestDate = new Date(timerData.get(timerData.size() - 1).getTimeStamp().getTime()); + } + } + + adjustedCpuData = adjustSamplingRate(oldData, from, to, aggregator); + } else if (leftAppend) { + // just append something on the left + Date leftDate = new Date(oldFromDate.getTime() - 1); + + List timerData = (List) dataAccessService.getDataObjectsFromToDate(template, from, leftDate); + + if (!timerData.isEmpty()) { + oldData.addAll(timerData); + oldFromDate = (Date) from.clone(); + } + + adjustedCpuData = adjustSamplingRate(oldData, from, to, aggregator); + } else { + // No update is needed here because we already have all the + // needed data + adjustedCpuData = adjustSamplingRate(oldData, from, to, aggregator); + } + + final List finalAdjustedCpuData = adjustedCpuData; + + // updating the plots in the UI thread + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + setUpperPlotData(finalAdjustedCpuData); + } + }); + } + + /** + * {@inheritDoc} + */ + public int getWeight(XYPlot subPlot) { + return weights.get(subPlot); + } + + /** + * {@inheritDoc} + */ + public Set getPreferenceIds() { + Set preferenceList = EnumSet.noneOf(PreferenceId.class); + if (getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) { + preferenceList.add(PreferenceId.LIVEMODE); + } + preferenceList.add(PreferenceId.TIMELINE); + preferenceList.add(PreferenceId.SAMPLINGRATE); + preferenceList.add(PreferenceId.UPDATE); + return preferenceList; + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DefaultMemoryPlotController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DefaultMemoryPlotController.java new file mode 100644 index 000000000..5ec345283 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DefaultMemoryPlotController.java @@ -0,0 +1,443 @@ +package info.novatec.inspectit.rcp.editor.graph.plot; + +import info.novatec.inspectit.cmr.service.IGlobalDataAccessService; +import info.novatec.inspectit.communication.data.MemoryInformationData; +import info.novatec.inspectit.communication.data.SystemInformationData; +import info.novatec.inspectit.indexing.aggregation.IAggregator; +import info.novatec.inspectit.indexing.aggregation.impl.MemoryInformationDataAggregator; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.geom.Ellipse2D; +import java.text.DateFormat; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.swt.widgets.Display; +import org.jfree.chart.axis.AxisLocation; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.labels.StandardXYToolTipGenerator; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.DeviationRenderer; +import org.jfree.data.RangeType; +import org.jfree.data.xy.YIntervalSeriesCollection; +import org.jfree.ui.RectangleInsets; + +/** + * This class creates a {@link XYPlot} containing the {@link MemoryInformationData} informations. + * + * @author Eduard Tudenhoefner + * @author Patrice Bouillet + * + */ +public class DefaultMemoryPlotController extends AbstractPlotController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.graph.memory"; + + /** + * The template of the {@link MemoryInformationData} object. + */ + private MemoryInformationData memoryTemplate; + + /** + * The template of the {@link SystemInformationData} object. + */ + private SystemInformationData systemTemplate; + + /** + * Indicates the weight of the upper {@link XYPlot}. + */ + private static final int WEIGHT_UPPER_PLOT = 1; + + /** + * Indicates the weight of the lower {@link XYPlot}. + */ + private static final int WEIGHT_LOWER_PLOT = 1; + + /** + * The upper {@link XYPlot}. + */ + private XYPlot upperPlot; + + /** + * The lower {@link XYPlot}. + */ + private XYPlot lowerPlot; + + /** + * The map containing the weight of the {@link XYPlot}s. + */ + private Map weights = new HashMap(); + + /** + * The {@link YIntervalSeriesImproved} for heap memory. + */ + private YIntervalSeriesImproved heapMemory; + + /** + * the {@link YIntervalSeriesImproved} for non-heap memory. + */ + private YIntervalSeriesImproved nonHeapMemory; + + /** + * The data access service to access the data on the CMR. + */ + private IGlobalDataAccessService dataAccessService; + + /** + * Old list containing some data objects which could be reused. + */ + private List oldData = Collections.emptyList(); + + /** + * The old from date. + */ + private Date oldFromDate = new Date(Long.MAX_VALUE); + + /** + * The old to date. + */ + private Date oldToDate = new Date(0); + + /** + * This represents the date of one of the objects which was received at some time in the past + * but was the one with the newest date. This is needed for not requesting some data of the CMR + * sometimes. + */ + private Date newestDate = new Date(0); + + /** + * {@link IAggregator}. + */ + private IAggregator aggregator = new MemoryInformationDataAggregator(); + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + memoryTemplate = new MemoryInformationData(); + memoryTemplate.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); + memoryTemplate.setSensorTypeIdent(inputDefinition.getIdDefinition().getSensorTypeId()); + memoryTemplate.setId(-1L); + + systemTemplate = new SystemInformationData(); + systemTemplate.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); + systemTemplate.setSensorTypeIdent(inputDefinition.getIdDefinition().getSensorTypeId()); + + dataAccessService = inputDefinition.getRepositoryDefinition().getGlobalDataAccessService(); + } + + /** + * {@inheritDoc} + */ + public List getPlots() { + upperPlot = initializeUpperPlot(); + lowerPlot = initializeLowerPlot(); + + List plots = new ArrayList(2); + plots.add(upperPlot); + plots.add(lowerPlot); + weights.put(upperPlot, WEIGHT_UPPER_PLOT); + weights.put(lowerPlot, WEIGHT_LOWER_PLOT); + + return plots; + } + + /** + * Initializes the upper plot. + * + * @return An instance of {@link XYPlot} + */ + private XYPlot initializeUpperPlot() { + heapMemory = new YIntervalSeriesImproved("heap memory"); + + YIntervalSeriesCollection yintervalseriescollection = new YIntervalSeriesCollection(); + yintervalseriescollection.addSeries(heapMemory); + + DeviationRenderer renderer = new DeviationRenderer(true, false); + renderer.setBaseShapesVisible(true); + renderer.setSeriesStroke(0, new BasicStroke(3.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + renderer.setSeriesFillPaint(0, new Color(255, 200, 200)); + renderer.setSeriesOutlineStroke(0, new BasicStroke(2.0f)); + renderer.setSeriesShape(0, new Ellipse2D.Double(-2.5, -2.5, 5.0, 5.0)); + renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT, DateFormat.getDateTimeInstance(), NumberFormat.getNumberInstance())); + + final NumberAxis rangeAxis = new NumberAxis("Heap / kbytes"); + rangeAxis.setRangeType(RangeType.POSITIVE); + + SystemInformationData systemData = (SystemInformationData) dataAccessService.getLastDataObject(systemTemplate); + + // set the range of y-axis only when we have the systeminformation + // sensor + if (systemData != null) { + // if the max heap size is not available set the range upper level to the double of + // initial heap size, if this is also unavailable then set it to 728MB + double maxHeapUpperRange; + if (systemData.getMaxHeapMemorySize() != -1) { + maxHeapUpperRange = systemData.getMaxHeapMemorySize() / 1024.0d; + } else if (systemData.getInitHeapMemorySize() != -1) { + maxHeapUpperRange = systemData.getInitHeapMemorySize() * 2 / 1024.0d; + } else { + maxHeapUpperRange = 728 * 1024 * 1024; + } + rangeAxis.setRange(0.0d, maxHeapUpperRange); + rangeAxis.setAutoRangeMinimumSize(maxHeapUpperRange); + } + + final XYPlot subplot = new XYPlot(yintervalseriescollection, null, rangeAxis, renderer); + subplot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0)); + subplot.setRangeAxisLocation(AxisLocation.TOP_OR_LEFT); + subplot.setRangeCrosshairVisible(true); + + return subplot; + } + + /** + * Updates the upper plot with the given input data. + * + * @param memoryData + * the input data. + */ + private void addUpperPlotData(List memoryData) { + for (MemoryInformationData data : memoryData) { + long usedHeapMemoryAvg = (data.getTotalUsedHeapMemorySize() / data.getCount()) / 1024; + heapMemory.add(data.getTimeStamp().getTime(), usedHeapMemoryAvg, data.getMinUsedHeapMemorySize() / 1024.0d, data.getMaxUsedHeapMemorySize() / 1024.0d, false); + } + heapMemory.fireSeriesChanged(); + } + + /** + * Removes all data from the upper plot and sets the {@link MemoryInformationData} objects on + * the plot. + * + * @param memoryData + * The data to set on the plot. + */ + private void setUpperPlotData(List memoryData) { + heapMemory.clear(); + addUpperPlotData(memoryData); + } + + /** + * Initializes the lower plot. + * + * @return An instance of {@link XYPlot} + */ + private XYPlot initializeLowerPlot() { + nonHeapMemory = new YIntervalSeriesImproved("non-heap memory"); + + YIntervalSeriesCollection yIntervalSeriesCollection = new YIntervalSeriesCollection(); + yIntervalSeriesCollection.addSeries(nonHeapMemory); + + DeviationRenderer renderer = new DeviationRenderer(true, false); + renderer.setBaseShapesVisible(true); + renderer.setSeriesStroke(0, new BasicStroke(3.0f)); + renderer.setSeriesOutlineStroke(0, new BasicStroke(2.0f)); + renderer.setSeriesShape(0, new Ellipse2D.Double(-2.5, -2.5, 5.0, 5.0)); + renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT, DateFormat.getDateTimeInstance(), NumberFormat.getNumberInstance())); + + final NumberAxis rangeAxis = new NumberAxis("Non-heap / kbytes"); + rangeAxis.setRangeType(RangeType.POSITIVE); + + SystemInformationData systemData = (SystemInformationData) dataAccessService.getLastDataObject(systemTemplate); + // set the range of y-axis only when we have the systeminformation + // sensor + if (systemData != null) { + // if the max non heap size is not available set the range upper level to the double of + // initial non heap size, if this is also unavailable then set it to 128MB + double maxNonHeapUpperRange; + if (systemData.getMaxNonHeapMemorySize() != -1) { + maxNonHeapUpperRange = systemData.getMaxNonHeapMemorySize() / 1024.0d; + } else if (systemData.getInitNonHeapMemorySize() != -1) { + maxNonHeapUpperRange = systemData.getInitNonHeapMemorySize() * 2 / 1024.0d; + } else { + maxNonHeapUpperRange = 128 * 1024 * 1024; + } + + if (maxNonHeapUpperRange > 0) { + rangeAxis.setRange(0, maxNonHeapUpperRange); + rangeAxis.setAutoRangeMinimumSize(maxNonHeapUpperRange); + } + } + + final XYPlot subplot = new XYPlot(yIntervalSeriesCollection, null, rangeAxis, renderer); + subplot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0)); + subplot.setRangeAxisLocation(AxisLocation.TOP_OR_LEFT); + subplot.setRangeCrosshairVisible(true); + + return subplot; + } + + /** + * Updates the lower plot with the given input data. + * + * @param memoryData + * the input data. + */ + private void addLowerPlotData(List memoryData) { + for (MemoryInformationData data : memoryData) { + // TODO adjust the fractional part + long usedNonHeapMemoryAvg = (data.getTotalUsedNonHeapMemorySize() / data.getCount()) / 1024; + nonHeapMemory.add(data.getTimeStamp().getTime(), usedNonHeapMemoryAvg, data.getMinUsedNonHeapMemorySize() / 1024.0d, data.getMaxUsedNonHeapMemorySize() / 1024.0d, false); + } + nonHeapMemory.fireSeriesChanged(); + } + + /** + * Removes all data from the lower plot and sets the {@link MemoryInformationData} objects on + * the plot. + * + * @param memoryData + * The data to set on the plot. + */ + private void setLowerPlotData(List memoryData) { + nonHeapMemory.clear(); + addLowerPlotData(memoryData); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public void update(final Date from, final Date to) { + Date dataNewestDate = new Date(0); + if (!oldData.isEmpty()) { + dataNewestDate = oldData.get(oldData.size() - 1).getTimeStamp(); + } + boolean leftAppend = from.before(oldFromDate); + // boolean rightAppend = to.after(dataNewestDate) && + // (to.equals(newestDate) || to.after(newestDate)); + boolean rightAppend = to.after(newestDate) || oldToDate.before(to); + + List adjustedMemoryInformationData = Collections.emptyList(); + + if (oldData.isEmpty() || to.before(oldFromDate) || from.after(dataNewestDate)) { + // the old data is empty or the range does not fit, thus we need + // to access the whole range + List data = (List) dataAccessService.getDataObjectsFromToDate(memoryTemplate, from, to); + + if (!data.isEmpty()) { + adjustedMemoryInformationData = adjustSamplingRate(data, from, to, aggregator); + + // we got some data, thus we can set the date + oldFromDate = (Date) from.clone(); + oldToDate = (Date) to.clone(); + if (newestDate.before(data.get(data.size() - 1).getTimeStamp())) { + newestDate = new Date(data.get(data.size() - 1).getTimeStamp().getTime()); + } + } + oldData = data; + } else if (leftAppend && rightAppend) { + // we have some data in between, but we need to append something + // to the start and to the end + Date rightDate = new Date(newestDate.getTime() + 1); + Date leftDate = new Date(oldFromDate.getTime() - 1); + + List rightData = (List) dataAccessService.getDataObjectsFromToDate(memoryTemplate, rightDate, to); + List leftData = (List) dataAccessService.getDataObjectsFromToDate(memoryTemplate, from, leftDate); + + if (!leftData.isEmpty()) { + oldData.addAll(0, leftData); + oldFromDate = (Date) from.clone(); + } + + if (!rightData.isEmpty()) { + oldData.addAll(rightData); + oldToDate = (Date) to.clone(); + if (newestDate.before(rightData.get(rightData.size() - 1).getTimeStamp())) { + newestDate = new Date(rightData.get(rightData.size() - 1).getTimeStamp().getTime()); + } + } + + adjustedMemoryInformationData = adjustSamplingRate(oldData, from, to, aggregator); + } else if (rightAppend) { + // just append something on the right + Date rightDate = new Date(newestDate.getTime() + 1); + + List timerData = (List) dataAccessService.getDataObjectsFromToDate(memoryTemplate, rightDate, to); + + if (!timerData.isEmpty()) { + oldData.addAll(timerData); + oldToDate = (Date) to.clone(); + if (newestDate.before(timerData.get(timerData.size() - 1).getTimeStamp())) { + newestDate = new Date(timerData.get(timerData.size() - 1).getTimeStamp().getTime()); + } + } + + adjustedMemoryInformationData = adjustSamplingRate(oldData, from, to, aggregator); + } else if (leftAppend) { + // just append something on the left + Date leftDate = new Date(oldFromDate.getTime() - 1); + + List timerData = (List) dataAccessService.getDataObjectsFromToDate(memoryTemplate, from, leftDate); + + if (!timerData.isEmpty()) { + oldData.addAll(timerData); + oldFromDate = (Date) from.clone(); + } + + adjustedMemoryInformationData = adjustSamplingRate(oldData, from, to, aggregator); + } else { + // No update is needed here because we already have all the + // needed data + adjustedMemoryInformationData = adjustSamplingRate(oldData, from, to, aggregator); + } + + final List finalAdjustedMemoryInformationData = adjustedMemoryInformationData; + + // updating the plots in the UI thread + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + setUpperPlotData(finalAdjustedMemoryInformationData); + setLowerPlotData(finalAdjustedMemoryInformationData); + } + }); + + } + + /** + * {@inheritDoc} + */ + public int getWeight(XYPlot subPlot) { + return weights.get(subPlot); + } + + /** + * {@inheritDoc} + */ + public Set getPreferenceIds() { + Set preferenceIds = EnumSet.noneOf(PreferenceId.class); + if (getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) { + preferenceIds.add(PreferenceId.LIVEMODE); + } + preferenceIds.add(PreferenceId.TIMELINE); + preferenceIds.add(PreferenceId.SAMPLINGRATE); + preferenceIds.add(PreferenceId.UPDATE); + return preferenceIds; + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DefaultThreadsPlotController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DefaultThreadsPlotController.java new file mode 100644 index 000000000..92903db87 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/DefaultThreadsPlotController.java @@ -0,0 +1,407 @@ +package info.novatec.inspectit.rcp.editor.graph.plot; + +import info.novatec.inspectit.cmr.service.IGlobalDataAccessService; +import info.novatec.inspectit.communication.data.ThreadInformationData; +import info.novatec.inspectit.indexing.aggregation.IAggregator; +import info.novatec.inspectit.indexing.aggregation.impl.ThreadInformationDataAggregator; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.geom.Ellipse2D; +import java.text.DateFormat; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.swt.widgets.Display; +import org.jfree.chart.axis.AxisLocation; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.labels.StandardXYToolTipGenerator; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.DeviationRenderer; +import org.jfree.data.RangeType; +import org.jfree.data.xy.YIntervalSeriesCollection; +import org.jfree.ui.RectangleInsets; + +/** + * This class creates a {@link XYPlot} containing the {@link ThreadInformationData} informations. + * + * @author Eduard Tudenhoefner + * @author Patrice Bouillet + * + */ +public class DefaultThreadsPlotController extends AbstractPlotController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.graph.threads"; + + /** + * The template of the {@link ThreadInformationData} object. + */ + private ThreadInformationData template; + + /** + * Indicates the weight of the upper {@link XYPlot}. + */ + private static final int WEIGHT_UPPER_PLOT = 1; + + /** + * Indicates the weight of the lower {@link XYPlot}. + */ + private static final int WEIGHT_LOWER_PLOT = 1; + + /** + * The upper {@link XYPlot}. + */ + private XYPlot upperPlot; + + /** + * The lower {@link XYPlot}. + */ + private XYPlot lowerPlot; + + /** + * The map containing the weight of the {@link XYPlot}s. + */ + private Map weights = new HashMap(); + + /** + * The {@link YIntervalSeriesImproved} for live threads. + */ + private YIntervalSeriesImproved liveThreads; + + /** + * The {@link YIntervalSeriesImproved} for peak threads. + */ + private YIntervalSeriesImproved peakThreads; + + /** + * The {@link YIntervalSeriesImproved} for daemon threads. + */ + private YIntervalSeriesImproved daemonThreads; + + /** + * The data access service to access the data on the CMR. + */ + private IGlobalDataAccessService dataAccessService; + + /** + * Old list containing some data objects which could be reused. + */ + private List oldData = Collections.emptyList(); + + /** + * The old from date. + */ + private Date oldFromDate = new Date(Long.MAX_VALUE); + + /** + * The old to date. + */ + private Date oldToDate = new Date(0); + + /** + * This represents the date of one of the objects which was received at some time in the past + * but was the one with the newest date. This is needed for not requesting some data of the CMR + * sometimes. + */ + private Date newestDate = new Date(0); + + /** + * {@link IAggregator}. + */ + IAggregator aggregator = new ThreadInformationDataAggregator(); + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + template = new ThreadInformationData(); + template.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); + template.setSensorTypeIdent(inputDefinition.getIdDefinition().getSensorTypeId()); + template.setId(-1L); + + dataAccessService = inputDefinition.getRepositoryDefinition().getGlobalDataAccessService(); + } + + /** + * {@inheritDoc} + */ + public List getPlots() { + upperPlot = initializeUpperPlot(); + lowerPlot = initializeLowerPlot(); + + List plots = new ArrayList(2); + plots.add(upperPlot); + plots.add(lowerPlot); + weights.put(upperPlot, WEIGHT_UPPER_PLOT); + weights.put(lowerPlot, WEIGHT_LOWER_PLOT); + + return plots; + } + + /** + * Initializes the upper plot with the given input data. + * + * @return An instance of {@link XYPlot} + */ + private XYPlot initializeUpperPlot() { + liveThreads = new YIntervalSeriesImproved("live"); + peakThreads = new YIntervalSeriesImproved("peak"); + + YIntervalSeriesCollection yIntervalSeriesCollection = new YIntervalSeriesCollection(); + yIntervalSeriesCollection.addSeries(liveThreads); + yIntervalSeriesCollection.addSeries(peakThreads); + + DeviationRenderer renderer = new DeviationRenderer(true, false); + renderer.setBaseShapesVisible(true); + renderer.setSeriesStroke(0, new BasicStroke(3.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); + renderer.setSeriesFillPaint(0, new Color(255, 200, 200)); + renderer.setSeriesOutlineStroke(0, new BasicStroke(2.0f)); + renderer.setSeriesShape(0, new Ellipse2D.Double(-2.5, -2.5, 5.0, 5.0)); + renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT, DateFormat.getDateTimeInstance(), NumberFormat.getNumberInstance())); + + final NumberAxis rangeAxis = new NumberAxis("Threads"); + rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); + rangeAxis.setAutoRangeMinimumSize(10.0d, false); + rangeAxis.setRangeType(RangeType.POSITIVE); + + final XYPlot subplot = new XYPlot(yIntervalSeriesCollection, null, rangeAxis, renderer); + subplot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0)); + subplot.setRangeAxisLocation(AxisLocation.TOP_OR_LEFT); + subplot.setRangeCrosshairVisible(true); + + return subplot; + } + + /** + * Updates the upper plot with the given input data. + * + * @param threadData + * the input data. + */ + private void addUpperPlotData(List threadData) { + for (ThreadInformationData data : threadData) { + float liveThreadAverage = ((float) data.getTotalThreadCount()) / data.getCount(); + float peakThreadAverage = ((float) data.getTotalPeakThreadCount()) / data.getCount(); + liveThreads.add(data.getTimeStamp().getTime(), liveThreadAverage, data.getMinThreadCount(), data.getMaxThreadCount(), false); + peakThreads.add(data.getTimeStamp().getTime(), peakThreadAverage, data.getMinPeakThreadCount(), data.getMaxPeakThreadCount(), false); + } + liveThreads.fireSeriesChanged(); + peakThreads.fireSeriesChanged(); + } + + /** + * Removes all data from the upper plot and sets the {@link ThreadInformationData} objects on + * the plot. + * + * @param threadData + * The data to set on the plot. + */ + private void setUpperPlotData(List threadData) { + liveThreads.clear(); + peakThreads.clear(); + addUpperPlotData(threadData); + } + + /** + * Initializes the lower plot with the given input data. + * + * @return An instance of {@link XYPlot}. + */ + private XYPlot initializeLowerPlot() { + daemonThreads = new YIntervalSeriesImproved("daemon"); + + YIntervalSeriesCollection yIntervalSeriesCollection = new YIntervalSeriesCollection(); + yIntervalSeriesCollection.addSeries(daemonThreads); + + DeviationRenderer renderer = new DeviationRenderer(true, false); + renderer.setBaseShapesVisible(true); + renderer.setSeriesStroke(0, new BasicStroke(3.0f)); + renderer.setSeriesOutlineStroke(0, new BasicStroke(2.0f)); + renderer.setSeriesShape(0, new Ellipse2D.Double(-2.5, -2.5, 5.0, 5.0)); + renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator(StandardXYToolTipGenerator.DEFAULT_TOOL_TIP_FORMAT, DateFormat.getDateTimeInstance(), NumberFormat.getNumberInstance())); + + final NumberAxis rangeAxis = new NumberAxis("Daemon threads"); + rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); + rangeAxis.setAutoRangeMinimumSize(10.0d, false); + rangeAxis.setRangeType(RangeType.POSITIVE); + + final XYPlot subplot = new XYPlot(yIntervalSeriesCollection, null, rangeAxis, renderer); + subplot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0)); + subplot.setRangeAxisLocation(AxisLocation.TOP_OR_LEFT); + subplot.setRangeCrosshairVisible(true); + + return subplot; + } + + /** + * Updates the lower plot with the given input data. + * + * @param threadData + * the input data. + */ + private void addLowerPlotData(List threadData) { + for (ThreadInformationData data : threadData) { + float daemonThreadAverage = ((float) data.getTotalDaemonThreadCount()) / data.getCount(); + daemonThreads.add(data.getTimeStamp().getTime(), daemonThreadAverage, data.getMinDaemonThreadCount(), data.getMaxDaemonThreadCount(), false); + } + daemonThreads.fireSeriesChanged(); + } + + /** + * Removes all data from the lower plot and sets the {@link ThreadInformationData} objects on + * the plot. + * + * @param threadData + * The data to set on the plot. + */ + private void setLowerPlotData(List threadData) { + daemonThreads.clear(); + addLowerPlotData(threadData); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public void update(Date from, Date to) { + Date dataNewestDate = new Date(0); + if (!oldData.isEmpty()) { + dataNewestDate = oldData.get(oldData.size() - 1).getTimeStamp(); + } + boolean leftAppend = from.before(oldFromDate); + // boolean rightAppend = to.after(dataNewestDate) && + // (to.equals(newestDate) || to.after(newestDate)); + boolean rightAppend = to.after(newestDate) || oldToDate.before(to); + + List adjustedThreadData = Collections.emptyList(); + + if (oldData.isEmpty() || to.before(oldFromDate) || from.after(dataNewestDate)) { + // the old data is empty or the range does not fit, thus we need + // to access the whole range + List data = (List) dataAccessService.getDataObjectsFromToDate(template, from, to); + + if (!data.isEmpty()) { + adjustedThreadData = adjustSamplingRate(data, from, to, aggregator); + + // we got some data, thus we can set the date + oldFromDate = (Date) from.clone(); + oldToDate = (Date) to.clone(); + if (newestDate.before(data.get(data.size() - 1).getTimeStamp())) { + newestDate = new Date(data.get(data.size() - 1).getTimeStamp().getTime()); + } + } + oldData = data; + } else if (leftAppend && rightAppend) { + // we have some data in between, but we need to append something + // to the start and to the end + Date rightDate = new Date(newestDate.getTime() + 1); + Date leftDate = new Date(oldFromDate.getTime() - 1); + + List rightData = (List) dataAccessService.getDataObjectsFromToDate(template, rightDate, to); + List leftData = (List) dataAccessService.getDataObjectsFromToDate(template, from, leftDate); + + if (!leftData.isEmpty()) { + oldData.addAll(0, leftData); + oldFromDate = (Date) from.clone(); + } + + if (!rightData.isEmpty()) { + oldData.addAll(rightData); + oldToDate = (Date) to.clone(); + if (newestDate.before(rightData.get(rightData.size() - 1).getTimeStamp())) { + newestDate = new Date(rightData.get(rightData.size() - 1).getTimeStamp().getTime()); + } + } + + adjustedThreadData = adjustSamplingRate(oldData, from, to, aggregator); + } else if (rightAppend) { + // just append something on the right + Date rightDate = new Date(newestDate.getTime() + 1); + + List timerData = (List) dataAccessService.getDataObjectsFromToDate(template, rightDate, to); + + if (!timerData.isEmpty()) { + oldData.addAll(timerData); + oldToDate = (Date) to.clone(); + if (newestDate.before(timerData.get(timerData.size() - 1).getTimeStamp())) { + newestDate = new Date(timerData.get(timerData.size() - 1).getTimeStamp().getTime()); + } + } + + adjustedThreadData = adjustSamplingRate(oldData, from, to, aggregator); + } else if (leftAppend) { + // just append something on the left + Date leftDate = new Date(oldFromDate.getTime() - 1); + + List timerData = (List) dataAccessService.getDataObjectsFromToDate(template, from, leftDate); + + if (!timerData.isEmpty()) { + oldData.addAll(timerData); + oldFromDate = (Date) from.clone(); + } + + adjustedThreadData = adjustSamplingRate(oldData, from, to, aggregator); + } else { + // No update is needed here because we already have all the + // needed data + adjustedThreadData = adjustSamplingRate(oldData, from, to, aggregator); + } + + final List finalAdjustedThreadData = adjustedThreadData; + + // updating the plots in the UI thread + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + setUpperPlotData(finalAdjustedThreadData); + setLowerPlotData(finalAdjustedThreadData); + } + }); + + } + + /** + * {@inheritDoc} + */ + public Set getPreferenceIds() { + Set preferenceList = EnumSet.noneOf(PreferenceId.class); + if (getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) { + preferenceList.add(PreferenceId.LIVEMODE); + } + preferenceList.add(PreferenceId.TIMELINE); + preferenceList.add(PreferenceId.SAMPLINGRATE); + preferenceList.add(PreferenceId.UPDATE); + return preferenceList; + } + + /** + * {@inheritDoc} + */ + public int getWeight(XYPlot subPlot) { + return weights.get(subPlot); + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/HttpTimerPlotController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/HttpTimerPlotController.java new file mode 100644 index 000000000..cb588040c --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/HttpTimerPlotController.java @@ -0,0 +1,323 @@ +package info.novatec.inspectit.rcp.editor.graph.plot; + +import info.novatec.inspectit.cmr.service.IHttpTimerDataAccessService; +import info.novatec.inspectit.communication.IAggregatedData; +import info.novatec.inspectit.communication.data.AggregatedHttpTimerData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.indexing.aggregation.IAggregator; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.HttpChartingInputDefinitionExtra; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory; +import info.novatec.inspectit.rcp.util.data.RegExAggregatedHttpTimerData; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.swt.widgets.Display; + +/** + * {@link PlotController} for displaying many Http requests in the graph. + * + * @author Ivan Senic + * + */ +public class HttpTimerPlotController extends AbstractTimerDataPlotController { + + /** + * {@link IAggregator}. + */ + private static final IAggregator AGGREGATOR = new SimpleHttpAggregator(); + + /** + * Templates that will be used for data display. Every template is one line in line chart. + */ + private List templates; + + /** + * List of {@link RegExAggregatedHttpTimerData} if regular expression is defined. + */ + private List regExTemplates; + + /** + * If true tag values from templates will be used in plotting. Otherwise URI is used. + */ + private boolean plotByTagValue = false; + + /** + * If true than regular expression transformation will be performed on the template URIs. + */ + private boolean regExTransformation = false; + + /** + * {@link IHttpTimerDataAccessService}. + */ + private IHttpTimerDataAccessService dataAccessService; + + /** + * List of displayed data. + */ + List displayedData = Collections.emptyList(); + + /** + * Date to display data to. + */ + Date toDate = new Date(0); + + /** + * Date to display data from. + */ + Date fromDate = new Date(Long.MAX_VALUE); + + /** + * Date that mark the last displayed data on the graph. + */ + Date latestDataDate = new Date(0); + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + if (inputDefinition.hasInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.HTTP_CHARTING_EXTRAS_MARKER)) { + HttpChartingInputDefinitionExtra inputDefinitionExtra = inputDefinition.getInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.HTTP_CHARTING_EXTRAS_MARKER); + templates = inputDefinitionExtra.getTemplates(); + plotByTagValue = inputDefinitionExtra.isPlotByTagValue(); + regExTransformation = inputDefinitionExtra.isRegExTransformation(); + if (regExTransformation) { + regExTemplates = inputDefinitionExtra.getRegExTemplates(); + } + } + + dataAccessService = inputDefinition.getRepositoryDefinition().getHttpTimerDataAccessService(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean showLegend() { + return templates.size() > 1; + } + + /** + * {@inheritDoc} + */ + @Override + public void update(Date from, Date to) { + // complete load if we have no data, or wanted time range is completely outside the current + boolean completeLoad = CollectionUtils.isEmpty(displayedData) || fromDate.after(to) || toDate.before(from); + // left append if currently displayed from date is after the new from date + boolean leftAppend = fromDate.after(from); + // right append if the currently displayed to date is before new to date or the date of the + // last data is before new date + boolean rightAppend = toDate.before(to) || latestDataDate.before(to); + + if (completeLoad) { + List httpTimerDatas = dataAccessService.getChartingHttpTimerDataFromDateToDate(templates, from, to, plotByTagValue); + if (CollectionUtils.isNotEmpty(httpTimerDatas)) { + fromDate = (Date) from.clone(); + toDate = (Date) to.clone(); + } + displayedData = httpTimerDatas; + } else { + if (rightAppend) { + Date startingFrom = new Date(latestDataDate.getTime() + 1); + List httpTimerDatas = dataAccessService.getChartingHttpTimerDataFromDateToDate(templates, startingFrom, to, plotByTagValue); + if (CollectionUtils.isNotEmpty(httpTimerDatas)) { + displayedData.addAll(httpTimerDatas); + toDate = (Date) to.clone(); + } + } + if (leftAppend) { + Date endingTo = new Date(fromDate.getTime() - 1); + List httpTimerDatas = dataAccessService.getChartingHttpTimerDataFromDateToDate(templates, from, endingTo, plotByTagValue); + if (CollectionUtils.isNotEmpty(httpTimerDatas)) { + displayedData.addAll(0, httpTimerDatas); + fromDate = (Date) from.clone(); + } + } + } + + // update the last displayed data + if (CollectionUtils.isNotEmpty(displayedData)) { + latestDataDate = new Date(displayedData.get(displayedData.size() - 1).getTimeStamp().getTime()); + } + + Map> map = new HashMap>(); + for (HttpTimerData data : displayedData) { + HttpTimerData template = findTemplateForData(data); + if (null == template) { + continue; + } + Object seriesKey = getSeriesKey(template); + List list = map.get(seriesKey); + if (null == list) { + list = new ArrayList(); + map.put(seriesKey, list); + } + list.add(data); + } + + for (Entry> entry : map.entrySet()) { + entry.setValue(adjustSamplingRate(entry.getValue(), from, to, AGGREGATOR)); + } + + final Map> finalMap = map; + + // update plots in UI thread + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + setDurationPlotData(finalMap); + setCountPlotData(finalMap); + } + }); + + } + + /** + * Finds matching template for the given {@link HttpTimerData} based on if regular expression + * transformation is active or not. + * + * @param httpTimerData + * Data to find matching template. + * @return Matching template of null if one can not be found. + */ + private HttpTimerData findTemplateForData(HttpTimerData httpTimerData) { + if (regExTransformation) { + for (RegExAggregatedHttpTimerData regExTemplate : regExTemplates) { + if (HttpTimerData.REQUEST_METHOD_MULTIPLE.equals(regExTemplate.getRequestMethod()) || Objects.equals(regExTemplate.getRequestMethod(), httpTimerData.getRequestMethod())) { + if (null != findTemplateForUriData(httpTimerData, regExTemplate.getAggregatedDataList(), true)) { + return regExTemplate; + } + } + } + } else if (plotByTagValue) { + return findTemplateForTagData(httpTimerData, templates); + } else { + return findTemplateForUriData(httpTimerData, templates, false); + } + return null; + } + + /** + * Finds matching template for the given {@link HttpTimerData} by uri. + * + * @param httpTimerData + * Data to find matching template. + * @param templates + * List of templates to search. + * @param checkOnlyUri + * If matching should be done only by uri. + * @return Matching template of null if one can not be found. + */ + private HttpTimerData findTemplateForUriData(HttpTimerData httpTimerData, List templates, boolean checkOnlyUri) { + for (HttpTimerData template : templates) { + if (Objects.equals(template.getUri(), httpTimerData.getUri())) { + if (!checkOnlyUri && HttpTimerData.REQUEST_METHOD_MULTIPLE.equals(template.getRequestMethod())) { + return template; + } else if (Objects.equals(template.getRequestMethod(), httpTimerData.getRequestMethod())) { + return template; + } + } + } + return null; + } + + /** + * Finds matching template for the given {@link HttpTimerData} by tag value. + * + * @param httpTimerData + * Data to find matching template. + * @param templates + * List of templates to search. + * @return Matching template of null if one can not be found. + */ + private HttpTimerData findTemplateForTagData(HttpTimerData httpTimerData, List templates) { + for (HttpTimerData template : templates) { + if (Objects.equals(template.getInspectItTaggingHeaderValue(), httpTimerData.getInspectItTaggingHeaderValue())) { + if (HttpTimerData.REQUEST_METHOD_MULTIPLE.equals(template.getRequestMethod()) || Objects.equals(template.getRequestMethod(), httpTimerData.getRequestMethod())) { + return template; + } + } + } + return null; + } + + /** + * Returns the series key for the {@link HttpTimerData} object. + * + * @param httpTimerData + * {@link HttpTimerData}. + * @return Key used to initialize the series and later on compare which series data should be + * added to. + */ + protected Comparable getSeriesKey(HttpTimerData httpTimerData) { + if (regExTransformation && httpTimerData instanceof RegExAggregatedHttpTimerData) { + return "Transformed URI: " + ((RegExAggregatedHttpTimerData) httpTimerData).getTransformedUri() + " [" + httpTimerData.getRequestMethod() + "]"; + } else { + if (plotByTagValue) { + return "Tag: " + httpTimerData.getInspectItTaggingHeaderValue() + " [" + httpTimerData.getRequestMethod() + "]"; + } else { + return "URI: " + httpTimerData.getUri() + " [" + httpTimerData.getRequestMethod() + "]"; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + protected List getTemplates() { + if (regExTransformation) { + return new ArrayList(regExTemplates); + } else { + return templates; + } + } + + /** + * Simple {@link IAggregator} to use for {@link HttpTimerData} aggregation, since we separate + * the data correctly before aggregation. + * + * @author Ivan Senic + * + */ + private static class SimpleHttpAggregator implements IAggregator { + + /** + * {@inheritDoc} + */ + @Override + public void aggregate(IAggregatedData aggregatedObject, HttpTimerData objectToAdd) { + aggregatedObject.aggregate(objectToAdd); + + } + + /** + * {@inheritDoc} + */ + @Override + public IAggregatedData getClone(HttpTimerData object) { + return new AggregatedHttpTimerData(); + } + + /** + * {@inheritDoc} + */ + @Override + public Object getAggregationKey(HttpTimerData object) { + return 1; + } + + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/PlotController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/PlotController.java new file mode 100644 index 000000000..b0fd926fe --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/PlotController.java @@ -0,0 +1,114 @@ +package info.novatec.inspectit.rcp.editor.graph.plot; + +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.preferences.control.SamplingRateControl.Sensitivity; +import info.novatec.inspectit.rcp.editor.preferences.control.samplingrate.SamplingRateMode; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; + +import java.util.Date; +import java.util.List; +import java.util.Set; + +import org.jfree.chart.plot.XYPlot; + +/** + * The interface for all plot controller. + * + * @author Patrice Bouillet + * + */ +public interface PlotController { + + /** + * Sets the input definition of this controller. + * + * @param inputDefinition + * The input definition. + */ + void setInputDefinition(InputDefinition inputDefinition); + + /** + * Sets the root editor. + * + * @param rootEditor + * The rootEditor to set. + */ + void setRootEditor(IRootEditor rootEditor); + + /** + * This method is used to retrieve the plots which are used in the whole chart. No data has to + * be requested from the server here, this is done in the {@link #update()} method. + * + * @return A list containing {@link XYPlot} classes which are used by JFreeChart to initialize + * the chart. + */ + List getPlots(); + + /** + * Returns the weight of a specific plot. + * + * @param subPlot + * The plot to calculate the weight. + * @return Returns the weight of the plot. + */ + int getWeight(XYPlot subPlot); + + /** + * This method obtains historical information from the DB for the timeframe denoted by its + * parameters. + *

+ * After fetching the historical data the upper and lower plot graphs get updated. + *

+ * Note that this method is not called in the UI thread because it is expected that this can + * be long running operation. Any access to the widgets in this method must be run in UI thread + * to ensure no InvalidThreadException occurs. + * + * @param from + * the timeframe's start date. + * @param to + * the timeframes's end date. + */ + void update(Date from, Date to); + + /** + * Sets the sampling rate. + * + * @param mode + * The mode. + * @param sensitivity + * The sensitivity of the mode. + */ + void setSamplingRate(SamplingRateMode mode, Sensitivity sensitivity); + + /** + * Returns all needed preference IDs. + * + * @return A {@link Set} containing all {@link PreferenceId}. Returning null is not + * permitted here. At least a {@link java.util.Collections#EMPTY_SET} should be + * returned. + */ + Set getPreferenceIds(); + + /** + * This method is called whenever something is changed in one of the preferences. + * + * @param preferenceEvent + * The event object containing the changed objects. + */ + void preferenceEventFired(PreferenceEvent preferenceEvent); + + /** + * If the legend in the chart should be shown. + * + * @return True if legend should be shown, otherwise false. + */ + boolean showLegend(); + + /** + * Disposes all plots etc. + */ + void dispose(); + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/TimerPlotController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/TimerPlotController.java new file mode 100644 index 000000000..da9ef90b6 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/TimerPlotController.java @@ -0,0 +1,204 @@ +package info.novatec.inspectit.rcp.editor.graph.plot; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.service.ICachedDataService; +import info.novatec.inspectit.cmr.service.IGlobalDataAccessService; +import info.novatec.inspectit.cmr.service.IHttpTimerDataAccessService; +import info.novatec.inspectit.cmr.service.cache.CachedDataService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.indexing.aggregation.IAggregator; +import info.novatec.inspectit.indexing.aggregation.impl.TimerDataAggregator; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.TimerDataChartingInputDefinitionExtra; +import info.novatec.inspectit.rcp.formatter.TextFormatter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.swt.widgets.Display; + +/** + * {@link PlotController} for displaying many Http requests in the graph. + * + * @author Ivan Senic + * + */ +public class TimerPlotController extends AbstractTimerDataPlotController { + + /** + * Templates that will be used for data display. Every template is one line in line chart. + */ + private List templates; + + /** + * {@link IHttpTimerDataAccessService}. + */ + private IGlobalDataAccessService dataAccessService; + + /** + * {@link CachedDataService}. + */ + private ICachedDataService cachedDataService; + + /** + * {@link IAggregator}. + */ + private IAggregator aggregator; + + /** + * List of displayed data. + */ + List displayedData = Collections.emptyList(); + + /** + * Date to display data to. + */ + Date toDate = new Date(0); + + /** + * Date to display data from. + */ + Date fromDate = new Date(Long.MAX_VALUE); + + /** + * Date that mark the last displayed data on the graph. + */ + Date latestDataDate = new Date(0); + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + if (inputDefinition.hasInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.TIMER_DATA_CHARTING_EXTRAS_MARKER)) { + TimerDataChartingInputDefinitionExtra inputDefinitionExtra = inputDefinition.getInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.TIMER_DATA_CHARTING_EXTRAS_MARKER); + templates = inputDefinitionExtra.getTemplates(); + } else { + TimerData template = new TimerData(); + template.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); + template.setSensorTypeIdent(inputDefinition.getIdDefinition().getSensorTypeId()); + template.setMethodIdent(inputDefinition.getIdDefinition().getMethodId()); + template.setId(-1L); + templates = Collections.singletonList(template); + } + + aggregator = new TimerDataAggregator(); + dataAccessService = inputDefinition.getRepositoryDefinition().getGlobalDataAccessService(); + cachedDataService = inputDefinition.getRepositoryDefinition().getCachedDataService(); + + } + + /** + * {@inheritDoc} + */ + @Override + public boolean showLegend() { + return templates.size() > 1; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public void update(Date from, Date to) { + List templates = new ArrayList(this.templates); + // complete load if we have no data, or wanted time range is completely outside the current + boolean completeLoad = CollectionUtils.isEmpty(displayedData) || fromDate.after(to) || toDate.before(from); + // left append if currently displayed from date is after the new from date + boolean leftAppend = fromDate.after(from); + // right append if the currently displayed to date is before new to date or the date of the + // last data is before new date + boolean rightAppend = toDate.before(to) || latestDataDate.before(to); + + if (completeLoad) { + List timerDatas = (List) dataAccessService.getTemplatesDataObjectsFromToDate(templates, from, to); + if (CollectionUtils.isNotEmpty(timerDatas)) { + fromDate = (Date) from.clone(); + toDate = (Date) to.clone(); + } + displayedData = timerDatas; + } else { + if (rightAppend) { + Date startingFrom = new Date(latestDataDate.getTime() + 1); + List timerDatas = (List) dataAccessService.getTemplatesDataObjectsFromToDate(templates, startingFrom, to); + if (CollectionUtils.isNotEmpty(timerDatas)) { + displayedData.addAll(timerDatas); + toDate = (Date) to.clone(); + } + } + if (leftAppend) { + Date endingTo = new Date(fromDate.getTime() - 1); + List timerDatas = (List) dataAccessService.getTemplatesDataObjectsFromToDate(templates, from, endingTo); + if (CollectionUtils.isNotEmpty(timerDatas)) { + displayedData.addAll(0, timerDatas); + fromDate = (Date) from.clone(); + } + } + } + + // update the last displayed data + if (CollectionUtils.isNotEmpty(displayedData)) { + latestDataDate = new Date(displayedData.get(displayedData.size() - 1).getTimeStamp().getTime()); + } + + Map> map = new HashMap>(); + for (TimerData data : displayedData) { + List list = map.get(getSeriesKey(data)); + if (null == list) { + list = new ArrayList(); + map.put(getSeriesKey(data), list); + } + list.add(data); + } + + for (Entry> entry : map.entrySet()) { + entry.setValue(adjustSamplingRate(entry.getValue(), from, to, aggregator)); + } + + // update plots in UI thread + final Map> finalMap = map; + Display.getDefault().asyncExec(new Runnable() { + public void run() { + setDurationPlotData(finalMap); + setCountPlotData(finalMap); + + // if we have only one list then we pass it to the root editor so that sub-views can + // set it + if (1 == finalMap.size()) { + final List datas = finalMap.entrySet().iterator().next().getValue(); + getRootEditor().setDataInput(datas); + } else { + getRootEditor().setDataInput(Collections. emptyList()); + } + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + protected Comparable getSeriesKey(TimerData template) { + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(template.getMethodIdent()); + return TextFormatter.getMethodString(methodIdent); + } + + /** + * {@inheritDoc} + */ + @Override + protected List getTemplates() { + return templates; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/YIntervalSeriesImproved.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/YIntervalSeriesImproved.java new file mode 100644 index 000000000..aaf959f11 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/YIntervalSeriesImproved.java @@ -0,0 +1,64 @@ +package info.novatec.inspectit.rcp.editor.graph.plot; + +import org.jfree.data.xy.YIntervalDataItem; +import org.jfree.data.xy.YIntervalSeries; + +/** + * @author Patrice Bouillet + * + */ +public class YIntervalSeriesImproved extends YIntervalSeries { + + /** + * Generated UID. + */ + private static final long serialVersionUID = 4341007484583713423L; + + /** + * Creates a new empty series. By default, items added to the series will be sorted into + * ascending order by x-value, and duplicate x-values will be allowed (these defaults can be + * modified with another constructor. + * + * @param key + * the series key (null not permitted). + */ + public YIntervalSeriesImproved(Comparable key) { + this(key, true, true); + } + + /** + * Constructs a new xy-series that contains no data. You can specify whether or not duplicate + * x-values are allowed for the series. + * + * @param key + * the series key (null not permitted). + * @param autoSort + * a flag that controls whether or not the items in the series are sorted. + * @param allowDuplicateXValues + * a flag that controls whether duplicate x-values are allowed. + */ + public YIntervalSeriesImproved(Comparable key, boolean autoSort, boolean allowDuplicateXValues) { + super(key, autoSort, allowDuplicateXValues); + } + + /** + * Adds a data item to the series. + * + * @param x + * the x-value. + * @param y + * the y-value. + * @param yLow + * the lower bound of the y-interval. + * @param yHigh + * the upper bound of the y-interval. + * @param notify + * a flag that controls whether or not a + * {@link org.jfree.data.general.SeriesChangeEvent} is sent to all registered + * listeners. + */ + public void add(double x, double y, double yLow, double yHigh, boolean notify) { + super.add(new YIntervalDataItem(x, y, yLow, yHigh), notify); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/ZoomListener.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/ZoomListener.java new file mode 100644 index 000000000..d570f5781 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/graph/plot/ZoomListener.java @@ -0,0 +1,19 @@ +package info.novatec.inspectit.rcp.editor.graph.plot; + +import java.util.EventListener; + +/** + * The interface that must be supported by classes that wish to receive notification of zooming + * events into an axis. + * + * @author Patrice Bouillet + * + */ +public interface ZoomListener extends EventListener { + + /** + * Method is executed whenever a zooming occurs. + */ + void zoomOccured(); + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/EditorPropertiesData.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/EditorPropertiesData.java new file mode 100644 index 000000000..b7bad11ce --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/EditorPropertiesData.java @@ -0,0 +1,248 @@ +package info.novatec.inspectit.rcp.editor.inputdefinition; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.swt.graphics.Image; + +import com.google.common.base.Objects; + +/** + * Class that defines the editor properties like title, image, description. + * + * @author Ivan Senic + * + */ +public class EditorPropertiesData { + + /** + * Enumeration that can be used to specify what should be used for editors part image and name. + * + * @author Ivan Senic + * + */ + public enum PartType { + + /** + * Value for group properties, can be used when specifying the part title and image. + */ + SENSOR, + + /** + * Value for view properties, can be used when specifying the part title and image. + */ + VIEW, + + } + + /** + * Flag for specifying what should be used as a part name. Defaults to + * {@link EditorPropertiesData#VIEW}. + */ + private PartType partNameFlag = PartType.VIEW; + + /** + * Flag for specifying what should be used as a part image. Defaults to + * {@link EditorPropertiesData#SENSOR}. + */ + private PartType partImageFlag = PartType.SENSOR; + + /** + * Group/sensor image. + */ + private Image sensorImage; + + /** + * Group/sensor name. + */ + private String sensorName = ""; + + /** + * The image of the view. + */ + private Image viewImage; + + /** + * View name. + */ + private String viewName = ""; + + /** + * @return Returns the part name. This {@link String} should be used for the editor tab. + */ + public String getPartName() { + switch (partNameFlag) { + case SENSOR: + return sensorName; + default: + return viewName; + } + } + + /** + * Sets what will be used as part name. + *

+ * Values acceptable are:
+ * {@link #SENSOR} - Uses sensor name as the part name
+ * {@link #VIEW} - Uses view name as the part name
+ * {@link #INFO} - Uses info as the part name + * + * @param partType + * Flag to set for part name. + */ + public void setPartNameFlag(PartType partType) { + partNameFlag = partType; + } + + /** + * @return Returns the part image. This {@link Image} should be used for the editor tab. + */ + public Image getPartImage() { + switch (partImageFlag) { + case VIEW: + return viewImage; + default: + return sensorImage; + } + } + + /** + * Sets what will be used as part image. + *

+ * Values acceptable are:
+ * {@link #SENSOR} - Uses sensor image as the part image
+ * {@link #VIEW} - Uses view image as the part image
+ * + * @param partType + * Flag to set for part image. + */ + public void setPartImageFlag(PartType partType) { + partImageFlag = partType; + } + + /** + * Gets {@link #partTooltip}. + * + * @return {@link #partTooltip} + */ + public String getPartTooltip() { + StringBuilder stringBuilder = new StringBuilder(sensorName); + if (StringUtils.isNotEmpty(viewName)) { + if (stringBuilder.length() > 0) { + stringBuilder.append(" > "); + } + stringBuilder.append(viewName); + } + return stringBuilder.toString(); + } + + /** + * Gets {@link #sensorImage}. + * + * @return {@link #sensorImage} + */ + public Image getSensorImage() { + return sensorImage; + } + + /** + * Sets {@link #sensorImage}. + * + * @param image + * New value for {@link #sensorImage} + */ + public void setSensorImage(Image image) { + this.sensorImage = image; + } + + /** + * Gets {@link #sensorName}. + * + * @return {@link #sensorName} + */ + public String getSensorName() { + return sensorName; + } + + /** + * Sets {@link #sensorName}. + * + * @param sensorName + * New value for {@link #sensorName} + */ + public void setSensorName(String sensorName) { + this.sensorName = sensorName; + } + + /** + * Gets {@link #viewImage}. + * + * @return {@link #viewImage} + */ + public Image getViewImage() { + return viewImage; + } + + /** + * Sets {@link #viewImage}. + * + * @param descriptionImage + * New value for {@link #viewImage} + */ + public void setViewImage(Image descriptionImage) { + this.viewImage = descriptionImage; + } + + /** + * Gets {@link #viewName}. + * + * @return {@link #viewName} + */ + public String getViewName() { + return viewName; + } + + /** + * Sets {@link #viewName}. + * + * @param viewName + * New value for {@link #viewName} + */ + public void setViewName(String viewName) { + this.viewName = viewName; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(partImageFlag, partNameFlag, sensorImage, sensorName, viewImage, viewName); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + EditorPropertiesData that = (EditorPropertiesData) object; + return Objects.equal(this.partImageFlag, that.partImageFlag) && Objects.equal(this.partNameFlag, that.partNameFlag) && Objects.equal(this.sensorImage, that.sensorImage) + && Objects.equal(this.sensorName, that.sensorName) && Objects.equal(this.viewImage, that.viewImage) && Objects.equal(this.viewName, that.viewName); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).add("group/sensor image", sensorImage).add("group/sensor", sensorName).add("viewName", viewImage).add("viewName", viewName).toString(); + } + +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/InputDefinition.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/InputDefinition.java new file mode 100644 index 000000000..966b07091 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/InputDefinition.java @@ -0,0 +1,437 @@ +package info.novatec.inspectit.rcp.editor.inputdefinition; + +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.IInputDefinitionExtra; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory.InputDefinitionExtraMarker; +import info.novatec.inspectit.rcp.model.SensorTypeEnum; +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; +import info.novatec.inspectit.rcp.preferences.PreferencesUtils; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.Assert; + +import com.google.common.base.Objects; + +/** + * This class is used as the input definition for all editors in the application. Nearly all + * parameters are optional in here. The actual processing is done in the respective editor which + * accesses these fields. + * + * @author Patrice Bouillet + * + */ +public class InputDefinition { + + /** + * This class holds the definition for the IDs used to identify the correct data objects on the + * CMR. + * + * @author Patrice Bouillet + * + */ + public static final class IdDefinition { + + /** + * If an ID is not in use ({@link #platformId}, {@link #sensorTypeId}, {@link #methodId}) it + * is set to this value to indicate this. + */ + public static final long ID_NOT_USED = 0; + + /** + * The ID of the platform for the view. Default is {@link ID_NOT_USED}. + */ + private long platformId = ID_NOT_USED; + + /** + * The ID of the sensor type for the view. Default is {@link ID_NOT_USED}. + */ + private long sensorTypeId = ID_NOT_USED; + + /** + * The ID of the method for the view.Default is {@link ID_NOT_USED}. + */ + private long methodId = ID_NOT_USED; + + /** + * Gets {@link #platformId}. + * + * @return {@link #platformId} + */ + public long getPlatformId() { + return platformId; + } + + /** + * Sets {@link #platformId}. + * + * @param platformId + * New value for {@link #platformId} + */ + public void setPlatformId(long platformId) { + this.platformId = platformId; + } + + /** + * Gets {@link #sensorTypeId}. + * + * @return {@link #sensorTypeId} + */ + public long getSensorTypeId() { + return sensorTypeId; + } + + /** + * Sets {@link #sensorTypeId}. + * + * @param sensorTypeId + * New value for {@link #sensorTypeId} + */ + public void setSensorTypeId(long sensorTypeId) { + this.sensorTypeId = sensorTypeId; + } + + /** + * Gets {@link #methodId}. + * + * @return {@link #methodId} + */ + public long getMethodId() { + return methodId; + } + + /** + * Sets {@link #methodId}. + * + * @param methodId + * New value for {@link #methodId} + */ + public void setMethodId(long methodId) { + this.methodId = methodId; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(platformId, sensorTypeId, methodId); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + IdDefinition that = (IdDefinition) object; + return Objects.equal(this.platformId, that.platformId) && Objects.equal(this.sensorTypeId, that.sensorTypeId) && Objects.equal(this.methodId, that.methodId); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).add("platformId", platformId).add("sensorTypeId", sensorTypeId).add("methodId", methodId).toString(); + } + + } + + /** + * If it is not necessary that every subview has its own {@link IdDefinition} object, then this + * ID can be used as the key for the map. + */ + public static final String GLOBAL_ID = "inspectit.subview.global"; + + /** + * The update rate of the automatic update mechanism. + */ + private long updateRate = PreferencesUtils.getLongValue(PreferencesConstants.REFRESH_RATE); + + /** + * If the view should be updated automatically. Default is false . + */ + private boolean automaticUpdate = false; + + /** + * The repository definition for this view to access the data. + */ + private RepositoryDefinition repositoryDefinition; + + /** + * The idMappings are used for the subviews / controllers to retrieve their specific + * {@link IdDefinition} object. The key of the map is one of the IDs defined in the controller + * classes. + */ + private Map> idMappings = new HashMap>(); + + /** + * The ID of the view. + */ + private SensorTypeEnum id; + + /** + * Editor data that define the title of the editor, description, icon and similar properties. + */ + private EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + + /** + * Map for holding the input definition extras. + */ + private Map, IInputDefinitionExtra> inputDefintionExtras = new HashMap, IInputDefinitionExtra>(); + + /** + * Gets {@link #repositoryDefinition}. + * + * @return {@link #repositoryDefinition} + */ + public RepositoryDefinition getRepositoryDefinition() { + return repositoryDefinition; + } + + /** + * Sets {@link #repositoryDefinition}. + * + * @param repositoryDefinition + * New value for {@link #repositoryDefinition} + */ + public void setRepositoryDefinition(RepositoryDefinition repositoryDefinition) { + Assert.isNotNull(repositoryDefinition); + + this.repositoryDefinition = repositoryDefinition; + } + + /** + * Gets {@link #updateRate}. + * + * @return {@link #updateRate} + */ + public long getUpdateRate() { + return updateRate; + } + + /** + * Sets {@link #updateRate}. + * + * @param updateRate + * New value for {@link #updateRate} + */ + public void setUpdateRate(long updateRate) { + this.updateRate = updateRate; + } + + /** + * Gets {@link #automaticUpdate}. + * + * @return {@link #automaticUpdate} + */ + public boolean isAutomaticUpdate() { + return automaticUpdate; + } + + /** + * Sets {@link #automaticUpdate}. + * + * @param automaticUpdate + * New value for {@link #automaticUpdate} + */ + public void setAutomaticUpdate(boolean automaticUpdate) { + this.automaticUpdate = automaticUpdate; + } + + /** + * Gets {@link #id}. + * + * @return {@link #id} + */ + public SensorTypeEnum getId() { + return id; + } + + /** + * Sets {@link #id}. + * + * @param id + * New value for {@link #id} + */ + public void setId(SensorTypeEnum id) { + this.id = id; + } + + /** + * Gets {@link #editorPropertiesData}. + * + * @return {@link #editorPropertiesData} + */ + public EditorPropertiesData getEditorPropertiesData() { + return editorPropertiesData; + } + + /** + * Sets {@link #editorPropertiesData}. + * + * @param editorPropertiesData + * New value for {@link #editorPropertiesData} + */ + public void setEditorPropertiesData(EditorPropertiesData editorPropertiesData) { + this.editorPropertiesData = editorPropertiesData; + } + + /** + * This is a convenience method to just define one {@link IdDefinition} object for this input + * definition. If one is already defined, a {@link RuntimeException} is thrown. + * + * @param idDefinition + * The ID definition to set. + */ + public void setIdDefinition(IdDefinition idDefinition) { + if (idMappings.containsKey(GLOBAL_ID)) { + throw new RuntimeException("Already defined an input definition!"); + } + + idMappings.put(GLOBAL_ID, new ArrayList()); + idMappings.get(GLOBAL_ID).add(idDefinition); + } + + /** + * This is the counterpart method to {@link #setIdDefinition(IdDefinition)}. This method can be + * called as often as needed in contrary to {@link #getAndRemoveIdDefinition(String)}. + * + * @return The single ID definition. + */ + public IdDefinition getIdDefinition() { + if (!idMappings.containsKey(GLOBAL_ID)) { + throw new RuntimeException("No unique id definition is set!"); + } + + return idMappings.get(GLOBAL_ID).get(0); + } + + /** + * Appends the {@link IdDefinition} to the end of the list for the given ID. + * + * @param id + * The ID of this {@link IdDefinition} object. + * @param idDefinition + * The {@link IdDefinition} object. + */ + public void addIdMapping(String id, IdDefinition idDefinition) { + if (!idMappings.containsKey(id)) { + idMappings.put(id, new ArrayList()); + } + + idMappings.get(id).add(idDefinition); + } + + /** + * This method retrieves the id definition for the specific id. Important here is that the id + * definition will be removed from the list. This is necessary so that the same subviews in one + * editor could get different id definitions. + *

+ * The order of the adding and retrieving of the subviews is important to visualize the correct + * graphs/tables etc. + * + * @param id + * The ID. + * @return The {@link IdDefinition} object. + */ + public IdDefinition getAndRemoveIdDefinition(String id) { + if (!idMappings.containsKey(id)) { + throw new RuntimeException("Key not found for ID definitions: " + id); + } + IdDefinition idDefinition = idMappings.get(id).remove(0); + if (idMappings.get(id).isEmpty()) { + idMappings.remove(id); + } + return idDefinition; + } + + /** + * Adds the input definition extra. + * + * @param extraMarker + * {@link InputDefinitionExtraMarker}. + * @param extra + * Extra to add. + * @param + * Type of extra. + */ + public void addInputDefinitonExtra(InputDefinitionExtraMarker extraMarker, E extra) { + inputDefintionExtras.put(extraMarker, extra); + } + + /** + * Returns the input definition extra. + * + * @param extraMarker + * Marker that defines the type of extra. + * @param + * Type of extra. + * @return Returns the input definition extra. + */ + @SuppressWarnings("unchecked") + public E getInputDefinitionExtra(InputDefinitionExtraMarker extraMarker) { + return (E) inputDefintionExtras.get(extraMarker); + } + + /** + * Returns if the {@link InputDefinition} contains the extra defined by the marker. + * + * @param extraMarker + * Marker. + * @return True if input definition has the extra marker. + */ + public boolean hasInputDefinitionExtra(InputDefinitionExtraMarker extraMarker) { + return inputDefintionExtras.containsKey(extraMarker); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(updateRate, automaticUpdate, repositoryDefinition, idMappings, id, editorPropertiesData, inputDefintionExtras); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + InputDefinition that = (InputDefinition) object; + return Objects.equal(this.updateRate, that.updateRate) && Objects.equal(this.automaticUpdate, that.automaticUpdate) && Objects.equal(this.repositoryDefinition, that.repositoryDefinition) + && Objects.equal(this.idMappings, that.idMappings) && Objects.equal(this.id, that.id) && Objects.equal(this.editorPropertiesData, that.editorPropertiesData) + && Objects.equal(this.inputDefintionExtras, that.inputDefintionExtras); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).add("updateRate", updateRate).add("automaticUpdate", automaticUpdate).add("repositoryDefinition", repositoryDefinition).add("idMappings", idMappings) + .add("id", id).add("editorPropertiesData", editorPropertiesData).add("inputDefintionExtras", inputDefintionExtras).toString(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/CombinedInvocationsInputDefinitionExtra.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/CombinedInvocationsInputDefinitionExtra.java new file mode 100644 index 000000000..0e1785e4a --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/CombinedInvocationsInputDefinitionExtra.java @@ -0,0 +1,75 @@ +package info.novatec.inspectit.rcp.editor.inputdefinition.extra; + +import info.novatec.inspectit.communication.data.InvocationSequenceData; + +import java.util.List; + +import com.google.common.base.Objects; + +/** + * Additional input definition data used for the combined invocations view. + * + * @author Ivan Senic + * + */ +public class CombinedInvocationsInputDefinitionExtra implements IInputDefinitionExtra { + + /** + * List of {@link InvocationSequenceData} templates that need to be combined. + */ + private List templates; + + /** + * Gets {@link #templates}. + * + * @return {@link #templates} + */ + public List getTemplates() { + return templates; + } + + /** + * Sets {@link #templates}. + * + * @param templates + * New value for {@link #templates} + */ + public void setTemplates(List templates) { + this.templates = templates; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(templates); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + CombinedInvocationsInputDefinitionExtra that = (CombinedInvocationsInputDefinitionExtra) object; + return Objects.equal(this.templates, that.templates); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).add("templates", templates).toString(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/ExceptionTypeInputDefinitionExtra.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/ExceptionTypeInputDefinitionExtra.java new file mode 100644 index 000000000..77466bf70 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/ExceptionTypeInputDefinitionExtra.java @@ -0,0 +1,71 @@ +package info.novatec.inspectit.rcp.editor.inputdefinition.extra; + +import com.google.common.base.Objects; + +/** + * Input definition extra for displaying the concrete exception type. + * + * @author Ivan Senic + * + */ +public class ExceptionTypeInputDefinitionExtra implements IInputDefinitionExtra { + + /** + * The detailed name of the {@link Throwable} object. + */ + private String throwableType; + + /** + * Gets {@link #throwableType}. + * + * @return {@link #throwableType} + */ + public String getThrowableType() { + return throwableType; + } + + /** + * Sets {@link #throwableType}. + * + * @param throwableType + * New value for {@link #throwableType} + */ + public void setThrowableType(String throwableType) { + this.throwableType = throwableType; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(throwableType); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + ExceptionTypeInputDefinitionExtra that = (ExceptionTypeInputDefinitionExtra) object; + return Objects.equal(this.throwableType, that.throwableType); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).add("throwableType", throwableType).toString(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/HttpChartingInputDefinitionExtra.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/HttpChartingInputDefinitionExtra.java new file mode 100644 index 000000000..9e07540cf --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/HttpChartingInputDefinitionExtra.java @@ -0,0 +1,135 @@ +package info.novatec.inspectit.rcp.editor.inputdefinition.extra; + +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.util.data.RegExAggregatedHttpTimerData; + +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; + +import com.google.common.base.Objects; + +/** + * Input definition extra for the HTTP charting editors. + * + * @author Ivan Senic + * + */ +public class HttpChartingInputDefinitionExtra implements IInputDefinitionExtra { + + /** + * List of templates that defines what will be included in charting. + */ + private List templates; + + /** + * List of {@link RegExAggregatedHttpTimerData} if regular expression should be defined. + */ + private List regExTemplates; + + /** + * Defines if plotting should be based on the {@link InspectIT} tag value. + * + * @see HttpTimerData#hasInspectItTaggingHeader() + */ + boolean plotByTagValue; + + /** + * Gets {@link #templates}. + * + * @return {@link #templates} + */ + public List getTemplates() { + return templates; + } + + /** + * Sets {@link #templates}. + * + * @param templates + * New value for {@link #templates} + */ + public void setTemplates(List templates) { + this.templates = templates; + } + + /** + * Gets {@link #regExTemplates}. + * + * @return {@link #regExTemplates} + */ + public List getRegExTemplates() { + return regExTemplates; + } + + /** + * Sets {@link #regExTemplates}. + * + * @param regExTemplates + * New value for {@link #regExTemplates} + */ + public void setRegExTemplates(List regExTemplates) { + this.regExTemplates = regExTemplates; + } + + /** + * Gets {@link #plotByTagValue}. + * + * @return {@link #plotByTagValue} + */ + public boolean isPlotByTagValue() { + return plotByTagValue; + } + + /** + * Sets {@link #plotByTagValue}. + * + * @param plotByTagValue + * New value for {@link #plotByTagValue} + */ + public void setPlotByTagValue(boolean plotByTagValue) { + this.plotByTagValue = plotByTagValue; + } + + /** + * @return Returns if reg ex transformation should be included in the graph. + */ + public boolean isRegExTransformation() { + return CollectionUtils.isNotEmpty(regExTemplates); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(templates, regExTemplates, plotByTagValue); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + HttpChartingInputDefinitionExtra that = (HttpChartingInputDefinitionExtra) object; + return Objects.equal(this.templates, that.templates) && Objects.equal(this.regExTemplates, that.regExTemplates) && Objects.equal(this.plotByTagValue, that.plotByTagValue); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).add("templates", templates).add("regExTemplates", regExTemplates).add("plotByTagValue", plotByTagValue).toString().toString(); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/IInputDefinitionExtra.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/IInputDefinitionExtra.java new file mode 100644 index 000000000..eb31cde8a --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/IInputDefinitionExtra.java @@ -0,0 +1,11 @@ +package info.novatec.inspectit.rcp.editor.inputdefinition.extra; + +/** + * Common interface for all input definition extras. + * + * @author Ivan Senic + * + */ +public interface IInputDefinitionExtra { + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/InputDefinitionExtrasMarkerFactory.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/InputDefinitionExtrasMarkerFactory.java new file mode 100644 index 000000000..d8a848f49 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/InputDefinitionExtrasMarkerFactory.java @@ -0,0 +1,137 @@ +package info.novatec.inspectit.rcp.editor.inputdefinition.extra; + +import com.google.common.base.Objects; + +/** + * Factory for {@link InputDefinitionExtraMarker}s. + * + * @author Ivan Senic + * + */ +public final class InputDefinitionExtrasMarkerFactory { + + /** + * Private constructor. + */ + private InputDefinitionExtrasMarkerFactory() { + } + + /** + * Marker for {@link NavigationSteppingInputDefinitionExtra}. + */ + public static final InputDefinitionExtraMarker NAVIGATION_STEPPING_EXTRAS_MARKER = new InputDefinitionExtraMarker() { + @Override + public Class getInputDefinitionExtraClass() { + return NavigationSteppingInputDefinitionExtra.class; + } + + }; + + /** + * Marker for {@link ExceptionTypeInputDefinitionExtra}. + */ + public static final InputDefinitionExtraMarker EXCEPTION_TYPE_EXTRAS_MARKER = new InputDefinitionExtraMarker() { + @Override + public Class getInputDefinitionExtraClass() { + return ExceptionTypeInputDefinitionExtra.class; + } + + }; + + /** + * Marker for {@link CombinedInvocationsInputDefinitionExtra}. + */ + public static final InputDefinitionExtraMarker COMBINED_INVOCATIONS_EXTRAS_MARKER = new InputDefinitionExtraMarker() { + @Override + public Class getInputDefinitionExtraClass() { + return CombinedInvocationsInputDefinitionExtra.class; + } + + }; + + /** + * Marker for {@link SqlStatementInputDefinitionExtra}. + */ + public static final InputDefinitionExtraMarker SQL_STATEMENT_EXTRAS_MARKER = new InputDefinitionExtraMarker() { + @Override + public Class getInputDefinitionExtraClass() { + return SqlStatementInputDefinitionExtra.class; + } + + }; + + /** + * Marker for {@link HttpChartingInputDefinitionExtra}. + */ + public static final InputDefinitionExtraMarker HTTP_CHARTING_EXTRAS_MARKER = new InputDefinitionExtraMarker() { + @Override + public Class getInputDefinitionExtraClass() { + return HttpChartingInputDefinitionExtra.class; + } + + }; + + /** + * Marker for {@link TimerDataChartingInputDefinitionExtra}. + */ + public static final InputDefinitionExtraMarker TIMER_DATA_CHARTING_EXTRAS_MARKER = new InputDefinitionExtraMarker() { + @Override + public Class getInputDefinitionExtraClass() { + return TimerDataChartingInputDefinitionExtra.class; + } + + }; + + /** + * Abstract class for input definition extras marker. + * + * @author Ivan Senic + * + * @param + * Type of input definition extra. + */ + public abstract static class InputDefinitionExtraMarker { + + /** + * @return Returns the class type of the input definition extra. + */ + public abstract Class getInputDefinitionExtraClass(); + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(getInputDefinitionExtraClass()); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + InputDefinitionExtraMarker that = (InputDefinitionExtraMarker) object; + return Objects.equal(this.getInputDefinitionExtraClass(), that.getInputDefinitionExtraClass()); + + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).add("inputDefintionExtraClass", getInputDefinitionExtraClass()).toString(); + } + + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/NavigationSteppingInputDefinitionExtra.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/NavigationSteppingInputDefinitionExtra.java new file mode 100644 index 000000000..9dc948e76 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/NavigationSteppingInputDefinitionExtra.java @@ -0,0 +1,100 @@ +package info.novatec.inspectit.rcp.editor.inputdefinition.extra; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.InvocationAwareData; + +import java.util.List; + +import com.google.common.base.Objects; + +/** + * Extended input definition data to support the navigation and stepping. + * + * @author Ivan Senic + * + */ +public class NavigationSteppingInputDefinitionExtra implements IInputDefinitionExtra { + + /** + * List of objects that define data from which navigate to was executed. + */ + private List invocationAwareDataList; + + /** + * List of the initial stepping template list. + */ + private List steppingTemplateList; + + /** + * Gets {@link #invocationAwareDataList}. + * + * @return {@link #invocationAwareDataList} + */ + public List getInvocationAwareDataList() { + return invocationAwareDataList; + } + + /** + * Sets {@link #invocationAwareDataList}. + * + * @param invocationAwareDataList + * New value for {@link #invocationAwareDataList} + */ + public void setInvocationAwareDataList(List invocationAwareDataList) { + this.invocationAwareDataList = invocationAwareDataList; + } + + /** + * Gets {@link #steppingTemplateList}. + * + * @return {@link #steppingTemplateList} + */ + public List getSteppingTemplateList() { + return steppingTemplateList; + } + + /** + * Sets {@link #steppingTemplateList}. + * + * @param steppingTemplateList + * New value for {@link #steppingTemplateList} + */ + public void setSteppingTemplateList(List steppingTemplateList) { + this.steppingTemplateList = steppingTemplateList; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(invocationAwareDataList, steppingTemplateList); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + NavigationSteppingInputDefinitionExtra that = (NavigationSteppingInputDefinitionExtra) object; + return Objects.equal(this.invocationAwareDataList, that.invocationAwareDataList) && Objects.equal(this.steppingTemplateList, that.steppingTemplateList); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).add("invocationAwareDataList", invocationAwareDataList).add("steppingTemplateList", steppingTemplateList).toString(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/SqlStatementInputDefinitionExtra.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/SqlStatementInputDefinitionExtra.java new file mode 100644 index 000000000..b05c6eb0e --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/SqlStatementInputDefinitionExtra.java @@ -0,0 +1,71 @@ +package info.novatec.inspectit.rcp.editor.inputdefinition.extra; + +import com.google.common.base.Objects; + +/** + * {@link IInputDefinitionExtra} that holds the SQL statement string. + * + * @author Ivan Senic + * + */ +public class SqlStatementInputDefinitionExtra implements IInputDefinitionExtra { + + /** + * SQL string. + */ + private String sql; + + /** + * Gets {@link #sql}. + * + * @return {@link #sql} + */ + public String getSql() { + return sql; + } + + /** + * Sets {@link #sql}. + * + * @param sql + * New value for {@link #sql} + */ + public void setSql(String sql) { + this.sql = sql; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), sql); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + SqlStatementInputDefinitionExtra that = (SqlStatementInputDefinitionExtra) object; + return Objects.equal(this.sql, that.sql); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).add("sql", sql).toString(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/TimerDataChartingInputDefinitionExtra.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/TimerDataChartingInputDefinitionExtra.java new file mode 100644 index 000000000..a80c279e0 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/inputdefinition/extra/TimerDataChartingInputDefinitionExtra.java @@ -0,0 +1,74 @@ +package info.novatec.inspectit.rcp.editor.inputdefinition.extra; + +import info.novatec.inspectit.communication.data.TimerData; + +import java.util.List; + +import com.google.common.base.Objects; + +/** + * Input definition extra for displaying many timer data templates on the chart. + * + * @author Ivan Senic + * + */ +public class TimerDataChartingInputDefinitionExtra implements IInputDefinitionExtra { + + /** + * List of templates that defines what will be included in charting. + */ + private List templates; + + /** + * Gets {@link #templates}. + * + * @return {@link #templates} + */ + public List getTemplates() { + return templates; + } + + /** + * Sets {@link #templates}. + * + * @param templates + * New value for {@link #templates} + */ + public void setTemplates(List templates) { + this.templates = templates; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(templates); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + TimerDataChartingInputDefinitionExtra that = (TimerDataChartingInputDefinitionExtra) object; + return Objects.equal(this.templates, that.templates); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return Objects.toStringHelper(this).add("templates", templates).toString(); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/FormPreferencePanel.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/FormPreferencePanel.java new file mode 100644 index 000000000..f731d95f4 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/FormPreferencePanel.java @@ -0,0 +1,938 @@ +package info.novatec.inspectit.rcp.editor.preferences; + +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.action.MenuAction; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId.LiveMode; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId.TimeResolution; +import info.novatec.inspectit.rcp.editor.preferences.control.IPreferenceControl; +import info.novatec.inspectit.rcp.handlers.MaximizeActiveViewHandler; +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; +import info.novatec.inspectit.rcp.preferences.PreferencesUtils; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.eclipse.core.commands.ParameterizedCommand; +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.bindings.Binding; +import org.eclipse.jface.bindings.TriggerSequence; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Section; +import org.eclipse.ui.internal.menus.CommandMessages; +import org.eclipse.ui.keys.IBindingService; +import org.eclipse.ui.menus.CommandContributionItem; +import org.eclipse.ui.menus.CommandContributionItemParameter; + +/** + * This is the class where the preference panel is created. + * + * @author Eduard Tudenhoefner + * @author Patrice Bouillet + * @author Stefan Siegl + */ +@SuppressWarnings("restriction") +public class FormPreferencePanel implements IPreferencePanel { + + /** + * ID of the preference panel. + */ + private String id; + + /** + * The used toolkit. + */ + private final FormToolkit toolkit; + + /** + * Callbacks which are containing the fire method which is executed whenever something is + * changed and updated. + */ + private List callbacks = new ArrayList(); + + /** + * The button for live mode switching. + */ + private Action switchLiveMode; + + /** + * The button for switching the preferences. + */ + private Action switchPreferences; + + /** + * THe button for switching the stepping control. + */ + private Action switchSteppingControl; + + /** + * The list of created preference controls. + */ + private List preferenceControlList = new ArrayList(); + + /** + * The created section. + */ + private Section section; + + /** + * The constructor which needs a {@link ViewController} reference. + * + * @param toolkit + * The Form toolkit which defines the used colors. + */ + public FormPreferencePanel(FormToolkit toolkit) { + Assert.isNotNull(toolkit); + + this.toolkit = toolkit; + this.id = UUID.randomUUID().toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getId() { + return id; + } + + /** + * {@inheritDoc} + */ + public void registerCallback(PreferenceEventCallback callback) { + Assert.isNotNull(callback); + + callbacks.add(callback); + } + + /** + * {@inheritDoc} + */ + public void removeCallback(PreferenceEventCallback callback) { + Assert.isNotNull(callback); + + callbacks.remove(callback); + } + + /** + * {@inheritDoc} + */ + public void fireEvent(PreferenceEvent event) { + for (PreferenceEventCallback callback : callbacks) { + callback.eventFired(event); + } + } + + /** + * {@inheritDoc} + */ + public void createPartControl(Composite parent, Set preferenceSet, InputDefinition inputDefinition, IToolBarManager toolBarManager) { + section = toolkit.createSection(parent, Section.NO_TITLE); + section.setText("Preferences"); + section.setLayout(new GridLayout(1, false)); + section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + section.setVisible(false); + + Composite innerComposite = toolkit.createComposite(section); + innerComposite.setLayout(new GridLayout(1, false)); + innerComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + // only add buttons and some controls if the set is not empty + if (null != preferenceSet && !preferenceSet.isEmpty()) { + if (null != toolBarManager) { + createButtons(preferenceSet, toolBarManager); + } + createPreferenceControls(innerComposite, preferenceSet); + } + + section.setClient(innerComposite); + section.setExpanded(false); + } + + /** + * {@inheritDoc} + */ + public void setVisible(boolean visible) { + section.setVisible(visible); + section.setExpanded(visible); + } + + /** + * {@inheritDoc} + */ + public void disableLiveMode() { + if (switchLiveMode.isChecked()) { + switchLiveMode.setChecked(false); + // switchPreferences.setEnabled(!switchPreferences.isEnabled()); + + createLiveModeEvent(); + } + } + + /** + * {@inheritDoc} + */ + public void update() { + if (switchPreferences.isChecked()) { + for (IPreferenceControl preferenceControl : preferenceControlList) { + PreferenceEvent event = new PreferenceEvent(preferenceControl.getControlGroupId()); + event.setPreferenceMap(preferenceControl.eventFired()); + fireEvent(event); + } + } + + fireEvent(new PreferenceEvent(PreferenceId.UPDATE)); + } + + /** + * {@inheritDoc} + */ + @Override + public void bufferCleared() { + fireEvent(new PreferenceEvent(PreferenceId.CLEAR_BUFFER)); + } + + /** + * {@inheritDoc} + */ + public void setSteppingControlChecked(boolean checked) { + if (null != switchSteppingControl) { + switchSteppingControl.setChecked(checked); + } + } + + /** + * Creates the preference controls in the preference control panel. + * + * @param parent + * The parent {@link Composite} to which the controls will be added. + * @param preferenceSet + * The set containing the preference IDs. + */ + private void createPreferenceControls(Composite parent, Set preferenceSet) { + for (PreferenceId preferenceIdEnum : preferenceSet) { + IPreferenceControl preferenceControl = PreferenceControlFactory.createPreferenceControls(parent, toolkit, preferenceIdEnum, this); + if (null != preferenceControl) { + preferenceControlList.add(preferenceControl); + } + } + } + + /** + * Creates the buttons for this panel. + * + * @param preferenceSet + * the list containing the preference ids. + * @param toolBarManager + * The tool bar manager. + */ + private void createButtons(Set preferenceSet, IToolBarManager toolBarManager) { + switchLiveMode = new SwitchLiveMode("Live"); + switchPreferences = new SwitchPreferences("Additional options"); // NOPMD + MenuAction menuAction = new MenuAction(); + menuAction.setImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_TOOL)); + menuAction.setToolTipText("Preferences"); + + // add the maximize to all forms, let eclipse hide it as declared in plugin.xml + IWorkbenchWindow workbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + Map params = new HashMap(); + params.put(MaximizeActiveViewHandler.PREFERENCE_PANEL_ID_PARAMETER, id); + CommandContributionItemParameter contributionParameters = new CommandContributionItemParameter(workbenchWindow, null, MaximizeActiveViewHandler.COMMAND_ID, params, InspectIT.getDefault() + .getImageDescriptor(InspectITImages.IMG_WINDOW), null, null, null, null, getTooltipTextForMaximizeContributionItem(), SWT.CHECK, null, true); + CommandContributionItem maximizeCommandContribution = new CommandContributionItem(contributionParameters); + toolBarManager.add(maximizeCommandContribution); + + if (preferenceSet.contains(PreferenceId.HTTP_AGGREGATION_REQUESTMETHOD)) { + toolBarManager.add(new SwitchHttpCategorizationRequestMethod("Include Request Method in Categorization")); + } + + if (preferenceSet.contains(PreferenceId.HTTP_URI_TRANSFORMING)) { + toolBarManager.add(new SwitchHttpUriTransformation("Apply sensor regular expression on URI")); + } + + if (preferenceSet.contains(PreferenceId.INVOCATION_SUBVIEW_MODE)) { + toolBarManager.add(new SwitchInvocationSubviewMode("Switch the tabbed views mode from/to aggregated/raw")); + } + + toolBarManager.add(new Separator()); + + if (preferenceSet.contains(PreferenceId.SAMPLINGRATE) || preferenceSet.contains(PreferenceId.TIMELINE)) { + toolBarManager.add(switchPreferences); + } + + if (preferenceSet.contains(PreferenceId.STEPPABLE_CONTROL)) { + switchSteppingControl = new SwitchSteppingControl("Stepping control"); + toolBarManager.add(switchSteppingControl); + } + + if (preferenceSet.contains(PreferenceId.LIVEMODE)) { + toolBarManager.add(switchLiveMode); + + // Refresh rate + MenuManager refreshMenuManager = new MenuManager("Refresh rate"); + long currentRefreshRate = PreferencesUtils.getLongValue(PreferencesConstants.REFRESH_RATE); + refreshMenuManager.add(new SetRefreshRateAction("5 (s)", 5000, currentRefreshRate)); + refreshMenuManager.add(new SetRefreshRateAction("10 (s)", 10000, currentRefreshRate)); + refreshMenuManager.add(new SetRefreshRateAction("30 (s)", 30000, currentRefreshRate)); + refreshMenuManager.add(new SetRefreshRateAction("60 (s)", 60000, currentRefreshRate)); + menuAction.addContributionItem(refreshMenuManager); + } + if (preferenceSet.contains(PreferenceId.UPDATE)) { + toolBarManager.add(new UpdateAction("Update")); // NOPMD + } + + if (preferenceSet.contains(PreferenceId.ITEMCOUNT)) { + int currentItemsToShow = PreferencesUtils.getIntValue(PreferencesConstants.ITEMS_COUNT_TO_SHOW); + MenuManager countMenuManager = new MenuManager("Item count to show"); + countMenuManager.add(new SetItemCountAction("10", 10, currentItemsToShow)); + countMenuManager.add(new SetItemCountAction("20", 20, currentItemsToShow)); + countMenuManager.add(new SetItemCountAction("50", 50, currentItemsToShow)); + countMenuManager.add(new SetItemCountAction("100", 100, currentItemsToShow)); + countMenuManager.add(new SetItemCountAction("200", 200, currentItemsToShow)); + countMenuManager.add(new SetItemCountAction("500", 500, currentItemsToShow)); + countMenuManager.add(new SetItemCountAction("All...", -1, currentItemsToShow)); + menuAction.addContributionItem(countMenuManager); + } + if (preferenceSet.contains(PreferenceId.FILTERDATATYPE)) { + Set> activeDataTypes = PreferencesUtils.getObject(PreferencesConstants.INVOCATION_FILTER_DATA_TYPES); + MenuManager dataTypeMenuManager = new MenuManager("Filter by DataType"); + dataTypeMenuManager.add(new FilterByDataTypeAction("Invocation Sequence Data", InvocationSequenceData.class, activeDataTypes)); + dataTypeMenuManager.add(new FilterByDataTypeAction("Timer Data", TimerData.class, activeDataTypes)); + dataTypeMenuManager.add(new FilterByDataTypeAction("Sql Statement Data", SqlStatementData.class, activeDataTypes)); + dataTypeMenuManager.add(new FilterByDataTypeAction("Http Timer Data", HttpTimerData.class, activeDataTypes)); + dataTypeMenuManager.add(new FilterByDataTypeAction("Exception Sensor Data", ExceptionSensorData.class, activeDataTypes)); + menuAction.addContributionItem(dataTypeMenuManager); + } + if (preferenceSet.contains(PreferenceId.INVOCFILTEREXCLUSIVETIME)) { + double currentInvocFilterExclusive = PreferencesUtils.getDoubleValue(PreferencesConstants.INVOCATION_FILTER_EXCLUSIVE_TIME); + MenuManager timeMenuManager = new MenuManager("Filter Details by Exclusive Time"); + timeMenuManager.add(new FilterByExclusiveTimeAction("No filter", Double.NaN, currentInvocFilterExclusive)); + // timeMenuManager.add(new Separator()); + timeMenuManager.add(new FilterByExclusiveTimeAction("0.1 ms", 0.1, currentInvocFilterExclusive)); + timeMenuManager.add(new FilterByExclusiveTimeAction("0.2 ms", 0.2, currentInvocFilterExclusive)); + timeMenuManager.add(new FilterByExclusiveTimeAction("0.5 ms", 0.5, currentInvocFilterExclusive)); + // timeMenuManager.add(new Separator()); + timeMenuManager.add(new FilterByExclusiveTimeAction("1 ms", 1.0, currentInvocFilterExclusive)); + timeMenuManager.add(new FilterByExclusiveTimeAction("2 ms", 2.0, currentInvocFilterExclusive)); + timeMenuManager.add(new FilterByExclusiveTimeAction("5 ms", 5.0, currentInvocFilterExclusive)); + timeMenuManager.add(new FilterByExclusiveTimeAction("10 ms", 10.0, currentInvocFilterExclusive)); + // timeMenuManager.add(new Separator()); + timeMenuManager.add(new FilterByExclusiveTimeAction("50 ms", 50.0, currentInvocFilterExclusive)); + timeMenuManager.add(new FilterByExclusiveTimeAction("100 ms", 100.0, currentInvocFilterExclusive)); + timeMenuManager.add(new FilterByExclusiveTimeAction("200 ms", 200.0, currentInvocFilterExclusive)); + timeMenuManager.add(new FilterByExclusiveTimeAction("500 ms", 500.0, currentInvocFilterExclusive)); + // timeMenuManager.add(new Separator()); + timeMenuManager.add(new FilterByExclusiveTimeAction("1 s", 1000.0, currentInvocFilterExclusive)); + timeMenuManager.add(new FilterByExclusiveTimeAction("1.5 s", 1500.0, currentInvocFilterExclusive)); + timeMenuManager.add(new FilterByExclusiveTimeAction("2 s", 2000.0, currentInvocFilterExclusive)); + timeMenuManager.add(new FilterByExclusiveTimeAction("5 s", 5000.0, currentInvocFilterExclusive)); + menuAction.addContributionItem(timeMenuManager); + } + if (preferenceSet.contains(PreferenceId.INVOCFILTERTOTALTIME)) { + double currentInvocFilterTotal = PreferencesUtils.getDoubleValue(PreferencesConstants.INVOCATION_FILTER_TOTAL_TIME); + MenuManager timeMenuManager = new MenuManager("Filter Details by Total Time"); + timeMenuManager.add(new FilterByTotalTimeAction("No filter", Double.NaN, currentInvocFilterTotal)); + // timeMenuManager.add(new Separator()); + timeMenuManager.add(new FilterByTotalTimeAction("0.1 ms", 0.1, currentInvocFilterTotal)); + timeMenuManager.add(new FilterByTotalTimeAction("0.2 ms", 0.2, currentInvocFilterTotal)); + timeMenuManager.add(new FilterByTotalTimeAction("0.5 ms", 0.5, currentInvocFilterTotal)); + // timeMenuManager.add(new Separator()); + timeMenuManager.add(new FilterByTotalTimeAction("1 ms", 1.0, currentInvocFilterTotal)); + timeMenuManager.add(new FilterByTotalTimeAction("2 ms", 2.0, currentInvocFilterTotal)); + timeMenuManager.add(new FilterByTotalTimeAction("5 ms", 5.0, currentInvocFilterTotal)); + timeMenuManager.add(new FilterByTotalTimeAction("10 ms", 10.0, currentInvocFilterTotal)); + // timeMenuManager.add(new Separator()); + timeMenuManager.add(new FilterByTotalTimeAction("50 ms", 50.0, currentInvocFilterTotal)); + timeMenuManager.add(new FilterByTotalTimeAction("100 ms", 100.0, currentInvocFilterTotal)); + timeMenuManager.add(new FilterByTotalTimeAction("200 ms", 200.0, currentInvocFilterTotal)); + timeMenuManager.add(new FilterByTotalTimeAction("500 ms", 500.0, currentInvocFilterTotal)); + // timeMenuManager.add(new Separator()); + timeMenuManager.add(new FilterByTotalTimeAction("1 s", 1000.0, currentInvocFilterTotal)); + timeMenuManager.add(new FilterByTotalTimeAction("1.5 s", 1500.0, currentInvocFilterTotal)); + timeMenuManager.add(new FilterByTotalTimeAction("2 s", 2000.0, currentInvocFilterTotal)); + timeMenuManager.add(new FilterByTotalTimeAction("5 s", 5000.0, currentInvocFilterTotal)); + menuAction.addContributionItem(timeMenuManager); + } + + if (preferenceSet.contains(PreferenceId.TIME_RESOLUTION)) { + MenuManager timeMenuManager = new MenuManager("Time Decimal Places"); + int currentDecimalPlaces = PreferencesUtils.getIntValue(PreferencesConstants.DECIMAL_PLACES); + timeMenuManager.add(new SetTimeDecimalPlaces("0", 0, currentDecimalPlaces)); + timeMenuManager.add(new SetTimeDecimalPlaces("1", 1, currentDecimalPlaces)); + timeMenuManager.add(new SetTimeDecimalPlaces("2", 2, currentDecimalPlaces)); + timeMenuManager.add(new SetTimeDecimalPlaces("3", 3, currentDecimalPlaces)); + menuAction.addContributionItem(timeMenuManager); + } + + // only add if there is really something in the menu + if (menuAction.getSize() > 0) { + toolBarManager.add(menuAction); + } + + toolBarManager.update(true); + } + + /** + * Due to the Eclipse bug this method will return the correct tool-tip text with correct binding + * sequence for the maximize active sub-view command. + *

+ * This method should be removed when Eclipse fixes the bug. + * + * @return Returns tool-tip text with key binding sequence. + */ + private String getTooltipTextForMaximizeContributionItem() { + String tooltipText = "Maximize Active Sub-View"; + + IBindingService bindingService = (IBindingService) PlatformUI.getWorkbench().getService(IBindingService.class); + TriggerSequence activeBinding = null; + Binding[] allBindings = bindingService.getBindings(); + for (Binding b : allBindings) { + ParameterizedCommand pCommand = b.getParameterizedCommand(); + if (null != pCommand) { + String commandId = pCommand.getId(); + if (ObjectUtils.equals(commandId, MaximizeActiveViewHandler.COMMAND_ID)) { + activeBinding = b.getTriggerSequence(); + break; + } + } + } + + if (activeBinding != null && !activeBinding.isEmpty()) { + String acceleratorText = activeBinding.format(); + if (acceleratorText != null && acceleratorText.length() != 0) { + tooltipText = NLS.bind(CommandMessages.Tooltip_Accelerator, tooltipText, acceleratorText); + } + } + + return tooltipText; + } + + /** + * Switches the Preference View on/off. + * + * @author Patrice Bouillet + * + */ + private final class SwitchPreferences extends Action { + /** + * Switches the preferences. + * + * @param text + * The text. + */ + private SwitchPreferences(String text) { + super(text, AS_CHECK_BOX); + setImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_PREFERENCES)); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + FormPreferencePanel.this.setVisible(isChecked()); + } + } + + /** + * Updates the Preferences. + * + * @author Patrice Bouillet + * + */ + private final class UpdateAction extends Action { + /** + * Updates an action. + * + * @param text + * The text. + */ + private UpdateAction(String text) { + super(text); + setImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_REFRESH)); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + FormPreferencePanel.this.update(); + } + } + + /** + * Switches the live mode. + * + * @author Eduard Tudenhoefner + * + */ + private final class SwitchLiveMode extends Action { + + /** + * Switches the Live Mode. + * + * @param text + * The text. + */ + public SwitchLiveMode(String text) { + super(text); + setImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_LIVE_MODE)); + setChecked(LiveMode.ACTIVE_DEFAULT); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + createLiveModeEvent(); + } + } + + /** + * Filters by the maximum number of elements shown. + * + * @author Stefan Siegl + */ + private final class SetItemCountAction extends Action { + /** the maximum number of elements shown. */ + private int limit; + + /** + * Constructor. + * + * @param text + * the text + * @param limit + * the maximum number of elements shown. + * @param currentItemsToShow + * current items to show, button will be selected if it matches the passed limit + */ + public SetItemCountAction(String text, int limit, int currentItemsToShow) { + super(text, Action.AS_RADIO_BUTTON); + this.limit = limit; + setChecked(currentItemsToShow == limit); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + if (isChecked()) { + PreferencesUtils.saveIntValue(PreferencesConstants.ITEMS_COUNT_TO_SHOW, limit, false); + Map countPreference = new HashMap(); + countPreference.put(PreferenceId.ItemCount.COUNT_SELECTION_ID, limit); + PreferenceEvent event = new PreferenceEvent(PreferenceId.ITEMCOUNT); + event.setPreferenceMap(countPreference); + fireEvent(event); + } + } + } + + /** + * Filters by data type. + * + * @author Ivan Senic + */ + private final class FilterByDataTypeAction extends Action { + + /** The sensor type. */ + private Class dataClass; + + /** + * Constructor. + * + * @param text + * the text + * @param dataClass + * the data type + * @param activeDatas + * currently active data types, button will be checked if the given data type is + * contained in the set + */ + public FilterByDataTypeAction(String text, Class dataClass, Set> activeDatas) { + super(text, Action.AS_CHECK_BOX); + this.dataClass = dataClass; + setChecked(activeDatas.contains(dataClass)); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + Set> activeDatas = PreferencesUtils.getObject(PreferencesConstants.INVOCATION_FILTER_DATA_TYPES); + if (isChecked() && !activeDatas.contains(dataClass)) { + activeDatas.add(dataClass); + PreferencesUtils.saveObject(PreferencesConstants.INVOCATION_FILTER_DATA_TYPES, activeDatas, false); + } else if (!isChecked() && activeDatas.contains(dataClass)) { + activeDatas.remove(dataClass); + PreferencesUtils.saveObject(PreferencesConstants.INVOCATION_FILTER_DATA_TYPES, activeDatas, false); + } + Map sensorTypePreference = new HashMap(); + sensorTypePreference.put(PreferenceId.DataTypeSelection.SENSOR_DATA_SELECTION_ID, dataClass); + PreferenceEvent event = new PreferenceEvent(PreferenceId.FILTERDATATYPE); + event.setPreferenceMap(sensorTypePreference); + fireEvent(event); + } + } + + /** + * Filters by exclusive time. + * + * @author Stefan Siegl + */ + private final class FilterByExclusiveTimeAction extends Action { + /** the time. */ + private double time; + + /** + * Constructor. + * + * @param text + * the text + * @param time + * the time + * @param currentInvocFilterExclusive + * current invocation filter exclusive time value, button will be checked if it + * matches passed time + */ + public FilterByExclusiveTimeAction(String text, double time, double currentInvocFilterExclusive) { + super(text, Action.AS_RADIO_BUTTON); + this.time = time; + setChecked(currentInvocFilterExclusive == time || (Double.isNaN(time)) && Double.isNaN(currentInvocFilterExclusive)); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + if (isChecked()) { + PreferencesUtils.saveDoubleValue(PreferencesConstants.INVOCATION_FILTER_EXCLUSIVE_TIME, time, false); + Map sensorTypePreference = new HashMap(); + sensorTypePreference.put(PreferenceId.InvocExclusiveTimeSelection.TIME_SELECTION_ID, new Double(time)); + PreferenceEvent event = new PreferenceEvent(PreferenceId.INVOCFILTEREXCLUSIVETIME); + event.setPreferenceMap(sensorTypePreference); + fireEvent(event); + } + } + } + + /** + * Filters by total time. + * + * @author Stefan Siegl + */ + private final class FilterByTotalTimeAction extends Action { + /** the time. */ + private double time; + + /** + * Constructor. + * + * @param text + * the text + * @param time + * the time + * @param currentInvocFilterTotal + * current invocation filter total time value, button will be checked if it + * matches passed time + */ + public FilterByTotalTimeAction(String text, double time, double currentInvocFilterTotal) { + super(text, Action.AS_RADIO_BUTTON); + this.time = time; + setChecked(currentInvocFilterTotal == time || (Double.isNaN(time)) && Double.isNaN(currentInvocFilterTotal)); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + if (isChecked()) { + PreferencesUtils.saveDoubleValue(PreferencesConstants.INVOCATION_FILTER_TOTAL_TIME, time, false); + Map sensorTypePreference = new HashMap(); + sensorTypePreference.put(PreferenceId.InvocTotalTimeSelection.TIME_SELECTION_ID, new Double(time)); + PreferenceEvent event = new PreferenceEvent(PreferenceId.INVOCFILTERTOTALTIME); + event.setPreferenceMap(sensorTypePreference); + fireEvent(event); + } + } + } + + /** + * Sets the automatic refresh rate. + * + * @author Stefan Siegl + */ + private final class SetRefreshRateAction extends Action { + + /** + * Refresh rate in ms. + */ + private long rate; + + /** + * Constructor. + * + * @param text + * the text + * @param rate + * the refresh rate + * @param currentRate + * current refresh rate, button will be checked if rate and current rate are the + * same + */ + public SetRefreshRateAction(String text, long rate, long currentRate) { + super(text, Action.AS_RADIO_BUTTON); + this.rate = rate; + setChecked(rate == currentRate); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + if (isChecked()) { + PreferencesUtils.saveLongValue(PreferencesConstants.REFRESH_RATE, rate, false); + Map refreshPreference = new HashMap(); + refreshPreference.put(LiveMode.REFRESH_RATE, rate); + PreferenceEvent event = new PreferenceEvent(PreferenceId.LIVEMODE); + event.setPreferenceMap(refreshPreference); + fireEvent(event); + } + } + } + + /** + * Creates and fires a new live mode event. + */ + private void createLiveModeEvent() { + Map livePreference = new HashMap(); + livePreference.put(PreferenceId.LiveMode.BUTTON_LIVE_ID, switchLiveMode.isChecked()); + PreferenceEvent event = new PreferenceEvent(PreferenceId.LIVEMODE); + event.setPreferenceMap(livePreference); + fireEvent(event); + } + + /** + * {@inheritDoc} + */ + public void dispose() { + for (IPreferenceControl preferenceControl : preferenceControlList) { + preferenceControl.dispose(); + } + + switchLiveMode = null; // NOPMD + } + + /** + * Action for turning the stepping control off and on. + * + * @author Ivan Senic + * + */ + private final class SwitchSteppingControl extends Action { + + /** + * Default constructor. + * + * @param text + * the action's text, or null if there is no text + * @see Action + */ + public SwitchSteppingControl(String text) { + super(text, AS_CHECK_BOX); + setImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_LOCATE_IN_HIERARCHY)); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + PreferenceEvent event = new PreferenceEvent(PreferenceId.STEPPABLE_CONTROL); + Map steppablePreference = new HashMap(); + steppablePreference.put(PreferenceId.SteppableControl.BUTTON_STEPPABLE_CONTROL_ID, this.isChecked()); + event.setPreferenceMap(steppablePreference); + fireEvent(event); + } + + } + + /** + * Sets the decimal places. + * + * @author Stefan Siegl + * + */ + private final class SetTimeDecimalPlaces extends Action { + + /** + * The number of decimal places. + * */ + private int decimalPlaces; + + /** + * Default constructor. + * + * @param text + * the action's text, or null if there is no text + * @param decimalPlaces + * the number of decimal places + * @param currentDecimalPlaces + * current decimal places, button will be checked if decimalPlaces and + * currentDecimalPlaces are same + * @see Action + */ + public SetTimeDecimalPlaces(String text, int decimalPlaces, int currentDecimalPlaces) { + super(text, Action.AS_RADIO_BUTTON); + this.decimalPlaces = decimalPlaces; + setChecked(decimalPlaces == currentDecimalPlaces); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + if (isChecked()) { + PreferencesUtils.saveIntValue(PreferencesConstants.DECIMAL_PLACES, decimalPlaces, false); + Map decimalPlacesPreference = new HashMap(); + decimalPlacesPreference.put(TimeResolution.TIME_DECIMAL_PLACES_ID, decimalPlaces); + PreferenceEvent event = new PreferenceEvent(PreferenceId.TIME_RESOLUTION); + event.setPreferenceMap(decimalPlacesPreference); + fireEvent(event); + } + } + } + + /** + * Option to switch between categorization based on request method or not. + * + * @author Stefan Siegl + */ + private final class SwitchHttpCategorizationRequestMethod extends Action { + + /** + * Default Constructor. + * + * @param text + * the action's text, or null if there is no text + */ + public SwitchHttpCategorizationRequestMethod(String text) { + super(text, AS_CHECK_BOX); + setImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_HTTP_AGGREGATION_REQUESTMESSAGE)); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + PreferenceEvent event = new PreferenceEvent(PreferenceId.HTTP_AGGREGATION_REQUESTMETHOD); + Map httpCategoriation = new HashMap(); + httpCategoriation.put(PreferenceId.HttpAggregationRequestMethod.BUTTON_HTTP_AGGREGATION_REQUESTMETHOD_ID, this.isChecked()); + event.setPreferenceMap(httpCategoriation); + fireEvent(event); + + // perform a refresh + update(); + } + } + + /** + * Option to active Http URI transformation with regular expression. + * + * @author Ivan Senic + * + */ + private final class SwitchHttpUriTransformation extends Action { + + /** + * Default Constructor. + * + * @param text + * the action's text, or null if there is no text + */ + public SwitchHttpUriTransformation(String text) { + super(text, AS_CHECK_BOX); + setImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_TRANSFORM)); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + PreferenceEvent event = new PreferenceEvent(PreferenceId.HTTP_URI_TRANSFORMING); + Map preferenceMap = new HashMap(); + preferenceMap.put(PreferenceId.HttpUriTransformation.URI_TRANSFORMATION_ACTIVE, this.isChecked()); + event.setPreferenceMap(preferenceMap); + fireEvent(event); + + // perform a refresh + update(); + } + } + + /** + * Action for switching the mode of the invocation subviews from/to raw/aggregated. + * + * @author Ivan Senic + * + */ + private final class SwitchInvocationSubviewMode extends Action { + + /** + * Default constructor. + * + * @param text + * Text on the action. + */ + public SwitchInvocationSubviewMode(String text) { + super(text, AS_CHECK_BOX); + setImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_HTTP_AGGREGATION_REQUESTMESSAGE)); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + PreferenceEvent event = new PreferenceEvent(PreferenceId.INVOCATION_SUBVIEW_MODE); + Map httpCategoriation = new HashMap(); + httpCategoriation.put(PreferenceId.InvocationSubviewMode.RAW, this.isChecked()); + event.setPreferenceMap(httpCategoriation); + fireEvent(event); + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/IPreferenceGroup.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/IPreferenceGroup.java new file mode 100644 index 000000000..f7085efec --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/IPreferenceGroup.java @@ -0,0 +1,11 @@ +package info.novatec.inspectit.rcp.editor.preferences; + +/** + * The marker interface for inner enumerations in the {@link PreferenceId}. + * + * @author Eduard Tudenhoefner + * + */ +public interface IPreferenceGroup { + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/IPreferencePanel.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/IPreferencePanel.java new file mode 100644 index 000000000..f6a3bc1a2 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/IPreferencePanel.java @@ -0,0 +1,106 @@ +package info.novatec.inspectit.rcp.editor.preferences; + +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; + +import java.util.Set; + +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.swt.widgets.Composite; + +/** + * The interface for all preference panels. + * + * @author Eduard Tudenhoefner + * + */ +public interface IPreferencePanel { + + /** + * Returns the ID of this preference panel. Each preference panel has a unique ID that can be + * used for a later reference. + * + * @return Returns the ID of this preference panel. Each preference panel has a unique ID that + * can be used for a later reference. + */ + String getId(); + + /** + * Creates the part control of this view. + * + * @param parent + * The parent used to draw the elements to. + * @param preferenceSet + * The set containing the preference IDs which are used to show the correct options. + * @param inputDefinition + * {@link InputDefinition} of the editor where preference panel will be created. + * @param toolBarManager + * The toolbar manager is needed if buttons are going to be displayed. Otherwise it + * can be null. + * + */ + void createPartControl(Composite parent, Set preferenceSet, InputDefinition inputDefinition, IToolBarManager toolBarManager); + + /** + * Registers a callback at this preference panel. + * + * @param callback + * The callback to register. + */ + void registerCallback(PreferenceEventCallback callback); + + /** + * Removes a callback from the preference panel. + * + * @param callback + * The callback to remove. + */ + void removeCallback(PreferenceEventCallback callback); + + /** + * Fires the event for all registered callbacks. + * + * @param event + * The event to fire. + */ + void fireEvent(PreferenceEvent event); + + /** + * Sets the visibility of the preference panel to show/hide. + * + * @param visible + * The visibility state. + */ + void setVisible(boolean visible); + + /** + * This method is called when an option is changed and should be applied to all the contained + * views. + */ + void update(); + + /** + * Disables the live mode in the preference panel. + */ + void disableLiveMode(); + + /** + * Signals that the buffer has been cleared and that all views that have register for the + * {@link PreferenceId#CLEAR_BUFFER} should delete input data. + */ + void bufferCleared(); + + /** + * Checking the switch stepping control button on preference panel if stepping button exists. + * + * @param checked + * True to be checked, false for not checked. + */ + void setSteppingControlChecked(boolean checked); + + /** + * Disposes this view / editor. + */ + void dispose(); + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/PreferenceControlFactory.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/PreferenceControlFactory.java new file mode 100644 index 000000000..02ca33a3a --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/PreferenceControlFactory.java @@ -0,0 +1,53 @@ +package info.novatec.inspectit.rcp.editor.preferences; + +import info.novatec.inspectit.rcp.editor.preferences.control.IPreferenceControl; +import info.novatec.inspectit.rcp.editor.preferences.control.SamplingRateControl; +import info.novatec.inspectit.rcp.editor.preferences.control.TimeLineControl; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * This factory creates the preference control groups and adds it to a list, because one class can + * have more then on control group. The list with the control groups will then be returned. + * + * @author Eduard Tudenhoefner + * + */ +public final class PreferenceControlFactory { + + /** + * The private constructor. + */ + private PreferenceControlFactory() { + } + + /** + * Creates and returns a new instance of {@link IPreferenceControl}. + * + * @param parent + * The {@link Composite} used to draw the elements to. + * @param toolkit + * The used toolkit. + * @param preferenceIdEnum + * The {@link PreferenceId} by which the {@link IPreferenceControl} will be created. + * @param preferencePanel + * Preference panel + * @return An instance of {@link IPreferenceControl}. + */ + public static IPreferenceControl createPreferenceControls(Composite parent, FormToolkit toolkit, PreferenceId preferenceIdEnum, IPreferencePanel preferencePanel) { + switch (preferenceIdEnum) { + case TIMELINE: + IPreferenceControl timeLineControl = new TimeLineControl(preferencePanel); + timeLineControl.createControls(parent, toolkit); + return timeLineControl; + case SAMPLINGRATE: + IPreferenceControl samplingRateControl = new SamplingRateControl(preferencePanel); + samplingRateControl.createControls(parent, toolkit); + return samplingRateControl; + default: + return null; + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/PreferenceEventCallback.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/PreferenceEventCallback.java new file mode 100644 index 000000000..f0f357aec --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/PreferenceEventCallback.java @@ -0,0 +1,78 @@ +package info.novatec.inspectit.rcp.editor.preferences; + +import java.util.Map; + +import org.eclipse.core.runtime.Assert; + +/** + * The callback is used by the {@link IPreferencePanel} implementations to fire the events. + * + * @author Patrice Bouillet + * + */ +public interface PreferenceEventCallback { + + /** + * This interface holds all the relevant data for the event. + * + * @author Patrice Bouillet + * + */ + class PreferenceEvent { + + /** + * The ID of this event. + */ + private final PreferenceId preferenceId; + + /** + * The hash map containing all the objects. + */ + private Map preferenceMap; + + /** + * Constructor which needs an {@link PreferenceId}. Throws {@link NullPointerException} if + * the preferenceId is null. + * + * @param preferenceId + * The preference ID. + */ + public PreferenceEvent(PreferenceId preferenceId) { + Assert.isNotNull(preferenceId); + + this.preferenceId = preferenceId; + } + + /** + * @return the preferenceId + */ + public PreferenceId getPreferenceId() { + return preferenceId; + } + + /** + * @param preferenceMap + * the preferenceMap to set + */ + public void setPreferenceMap(Map preferenceMap) { + this.preferenceMap = preferenceMap; + } + + /** + * @return the preferenceMap + */ + public Map getPreferenceMap() { + return preferenceMap; + } + + } + + /** + * This method is called whenever the preferences are changed. + * + * @param preferenceEvent + * The event object containing the changed objects. + */ + void eventFired(PreferenceEvent preferenceEvent); + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/PreferenceId.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/PreferenceId.java new file mode 100644 index 000000000..dc77f7116 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/PreferenceId.java @@ -0,0 +1,188 @@ +package info.novatec.inspectit.rcp.editor.preferences; + +/** + * The enumeration set for the unique preference group ids. By adding new enumerations you should + * also create an inner public enumeration class which contains the associated control ids. + * + * @author Eduard Tudenhoefner + * + */ +public enum PreferenceId { + + /** + * The identifiers of the different control groups. + */ + TIMELINE, SAMPLINGRATE, LIVEMODE, UPDATE, ITEMCOUNT, FILTERDATATYPE, INVOCFILTEREXCLUSIVETIME, INVOCFILTERTOTALTIME, CLEAR_BUFFER, STEPPABLE_CONTROL, TIME_RESOLUTION, HTTP_AGGREGATION_REQUESTMETHOD, HTTP_URI_TRANSFORMING, INVOCATION_SUBVIEW_MODE; + + /** + * Inner enumeration for TIMELINE. + * + * @author Eduard Tudenhoefner + * + */ + public enum TimeLine implements IPreferenceGroup { + /** + * The identifiers of the elements in the + * {@link info.novatec.inspectit.rcp.editor.preferences.control.TimeLineControl}. + */ + FROM_DATE_ID, TO_DATE_ID; + + /** + * Defines the default time line period displayed. + */ + public static final long TIMELINE_DEFAULT = 10 * 60 * 1000; + } + + /** + * Inner enumeration for SAMPLINGRATE. + * + * @author Eduard Tudenhoefner + * + */ + public enum SamplingRate implements IPreferenceGroup { + /** + * The identifiers of the elements in the + * {@link info.novatec.inspectit.rcp.editor.preferences.control.SamplingRateControl} . + */ + SLIDER_ID, DIVIDER_ID, TIMEFRAME_DIVIDER_ID; + } + + /** + * Inner enumeration for LIVEMODE. + * + * @author Eduard Tudenhoefner + * + */ + public enum LiveMode implements IPreferenceGroup { + /** + * The identifier for the live button. + */ + BUTTON_LIVE_ID, REFRESH_RATE; + + /** + * Defines if the live mode is active by default. + */ + public static final boolean ACTIVE_DEFAULT = false; + } + + /** + * Inner enumeration for ITEMCOUNT. + * + * @author Patrice Bouillet + * + */ + public enum ItemCount implements IPreferenceGroup { + /** + * The identifier for the item count. + */ + COUNT_SELECTION_ID; + } + + /** + * Inner enumeration for the FILTERDATATYPE. + * + * @author Ivan Senic + * + */ + public enum DataTypeSelection implements IPreferenceGroup { + /** + * The identifier for the sensor data selections. + */ + SENSOR_DATA_SELECTION_ID; + } + + /** + * Inner enumeration for the INVOCEXCLUSIVETIMESELECTION. + * + * @author Patrice Bouillet + * + */ + public enum InvocExclusiveTimeSelection implements IPreferenceGroup { + /** + * The identifier for the time selection. + */ + TIME_SELECTION_ID; + } + + /** + * Inner enumeration for the INVOCTOTALTIMESELECTION. + * + * @author Patrice Bouillet + * + */ + public enum InvocTotalTimeSelection implements IPreferenceGroup { + /** + * The identifier for the time selection. + */ + TIME_SELECTION_ID; + } + + /** + * Inner enumeration for STEPPABLE_CONTROL. + * + * @author Ivan Senic + * + */ + public enum SteppableControl implements IPreferenceGroup { + /** + * The identifier for the switch stepping control button. + */ + BUTTON_STEPPABLE_CONTROL_ID; + } + + /** + * Inner enumeration for TIME_RESOLUTION. + * + * @author Ivan Senic + * + */ + public enum TimeResolution implements IPreferenceGroup { + /** + * The identifier for the definition of decimal places. + */ + TIME_DECIMAL_PLACES_ID; + } + + /** + * Inner enumeration for HTTP_AGGREGATION_REQUESTMETHOD. + * + * @author Stefan Siegl + */ + public enum HttpAggregationRequestMethod implements IPreferenceGroup { + /** + * The identifier for the switch stepping control button. + */ + BUTTON_HTTP_AGGREGATION_REQUESTMETHOD_ID; + } + + /** + * Inner enumeration for HTTP_URI_TRANSFORMING. + * + * @author Ivan Senic + * + */ + public enum HttpUriTransformation implements IPreferenceGroup { + + /** + * The identifier that defines if URI transformation is active. + */ + URI_TRANSFORMATION_ACTIVE; + + /** + * Defines if the live mode is active by default. + */ + public static final boolean DEFAULT = false; + } + + /** + * Inner enumeration for INVOCATION_SUBVIEW_MODE. + * + * @author Ivan Senic + */ + public enum InvocationSubviewMode implements IPreferenceGroup { + /** + * The identifier to state that the mode in subview of invocation is raw or not. + */ + RAW; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/AbstractPreferenceControl.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/AbstractPreferenceControl.java new file mode 100644 index 000000000..e1087084b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/AbstractPreferenceControl.java @@ -0,0 +1,37 @@ +package info.novatec.inspectit.rcp.editor.preferences.control; + +import info.novatec.inspectit.rcp.editor.preferences.IPreferencePanel; + +/** + * Abstract class for all preference controls. + * + * @author Ivan Senic + * + */ +public abstract class AbstractPreferenceControl implements IPreferenceControl { + + /** + * Preference panel. + */ + private IPreferencePanel preferencePanel; + + /** + * Default constructor. + * + * @param preferencePanel + * Preference panel. + */ + public AbstractPreferenceControl(IPreferencePanel preferencePanel) { + this.preferencePanel = preferencePanel; + } + + /** + * Gets {@link #preferencePanel}. + * + * @return {@link #preferencePanel} + */ + protected IPreferencePanel getPreferencePanel() { + return preferencePanel; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/IPreferenceControl.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/IPreferenceControl.java new file mode 100644 index 000000000..458c16431 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/IPreferenceControl.java @@ -0,0 +1,50 @@ +package info.novatec.inspectit.rcp.editor.preferences.control; + +import info.novatec.inspectit.rcp.editor.preferences.IPreferenceGroup; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; + +import java.util.Map; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * The interface for all concrete preference control creators. + * + * @author Eduard Tudenhoefner + * + */ +public interface IPreferenceControl { + + /** + * Returns the unique {@link PreferenceId} of this preference control group. + * + * @return The unique {@link PreferenceId} of this preference control group. + */ + PreferenceId getControlGroupId(); + + /** + * Creates the controls of the control group. + * + * @param parent + * The {@link Composite} to which the controls will be added. + * @param toolkit + * The used toolkit. + * @return The {@link Composite} containing the controls. + */ + Composite createControls(Composite parent, FormToolkit toolkit); + + /** + * This method gets called when pressing the Update Button in the global control of the panel. + * The method gets the actual data values from all controls and puts them in a {@link Map}. + * + * @return The {@link Map} containing the data values from the control items. + */ + Map eventFired(); + + /** + * Disposes the preference control. + */ + void dispose(); + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/SamplingRateControl.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/SamplingRateControl.java new file mode 100644 index 000000000..59fce1326 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/SamplingRateControl.java @@ -0,0 +1,225 @@ +package info.novatec.inspectit.rcp.editor.preferences.control; + +import info.novatec.inspectit.rcp.editor.preferences.IPreferenceGroup; +import info.novatec.inspectit.rcp.editor.preferences.IPreferencePanel; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Scale; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Section; + +/** + * This class creates a control group with a sampling rate slider in the + * {@link info.novatec.inspectit.rcp.editor.preferences.FormPreferencePanel}. + * + * @author Eduard Tudenhoefner + * + */ +public class SamplingRateControl extends AbstractPreferenceControl implements IPreferenceControl { + + /** + * The unique id of this preference control. + */ + private static final PreferenceId CONTROL_GROUP_ID = PreferenceId.SAMPLINGRATE; + + /** + * The sampling rate slider. + */ + private Scale slider = null; + + /** + * The radio button for timeframe selection. + */ + private Button timeframeModeButton = null; + + /** + * The available sensitivity modes. + * + * @author Patrice Bouillet + * + */ + public enum Sensitivity { + /** No sensitivity. */ + NO_SENSITIVITY(0), + /** 'Very fine' sensitivity. */ + VERY_FINE(200), + /** 'Fine' sensitivity. */ + FINE(120), + /** 'Medium' sensitivity. */ + MEDIUM(75), + /** 'Coarse' sensitivity. */ + COARSE(30), + /** 'Very coarse' sensitivity. */ + VERY_COARSE(15); + + /** + * The value. + */ + private int value; + + /** + * The constructor needing the specific value. + * + * @param value + * The value. + */ + private Sensitivity(int value) { + this.value = value; + } + + /** + * Returns the sensitivity value. + * + * @return The value. + */ + public int getValue() { + return value; + } + + /** + * Converts an ordinal into a {@link Sensitivity}. + * + * @param i + * The ordinal. + * @return The appropriate column. + */ + public static Sensitivity fromOrd(int i) { + if (i < 0 || i >= Sensitivity.values().length) { + throw new IndexOutOfBoundsException("Invalid ordinal"); + } + return Sensitivity.values()[i]; + } + } + + /** + * The default sensitivity. + */ + public static final Sensitivity DEFAULT_SENSITIVITY = Sensitivity.MEDIUM; + + /** + * Default constructor. + * + * @param preferencePanel + * Preference panel. + */ + public SamplingRateControl(IPreferencePanel preferencePanel) { + super(preferencePanel); + } + + /** + * {@inheritDoc} + */ + public Composite createControls(Composite parent, FormToolkit toolkit) { + Section section = toolkit.createSection(parent, Section.TITLE_BAR); + section.setText("Sampling Rate"); + section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + Composite composite = toolkit.createComposite(section); + section.setClient(composite); + + GridLayout layout = new GridLayout(4, false); + layout.marginLeft = 10; + layout.horizontalSpacing = 10; + composite.setLayout(layout); + GridData gridData = new GridData(SWT.MAX, SWT.DEFAULT); + gridData.grabExcessHorizontalSpace = true; + composite.setLayoutData(gridData); + + final Label sliderLabel = toolkit.createLabel(composite, "no sensitivity selected", SWT.LEFT); + GridData data = new GridData(SWT.FILL, SWT.FILL, false, false); + data.widthHint = sliderLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; + sliderLabel.setLayoutData(data); + + slider = new Scale(composite, SWT.HORIZONTAL); + toolkit.adapt(slider, true, true); + slider.setMinimum(0); + slider.setMaximum(Sensitivity.values().length - 1); + slider.setIncrement(1); + slider.setSize(200, 10); + slider.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + slider.addSelectionListener(new SelectionAdapter() { + /** + * {@inheritDoc} + */ + @Override + public void widgetSelected(SelectionEvent event) { + Sensitivity sensitivity = Sensitivity.fromOrd(slider.getSelection()); + + switch (sensitivity) { + case NO_SENSITIVITY: + sliderLabel.setText("no sensitivity selected"); + break; + case VERY_FINE: + sliderLabel.setText("very fine"); + break; + case FINE: + sliderLabel.setText("fine"); + break; + case MEDIUM: + sliderLabel.setText("medium"); + break; + case COARSE: + sliderLabel.setText("coarse"); + break; + case VERY_COARSE: + sliderLabel.setText("very coarse"); + break; + default: + break; + } + } + }); + slider.setSelection(DEFAULT_SENSITIVITY.ordinal()); + slider.notifyListeners(SWT.Selection, null); + + Label modeLabel = toolkit.createLabel(composite, "Sampling Rate Mode: ", SWT.LEFT); + modeLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + + timeframeModeButton = toolkit.createButton(composite, "Timeframe dividing", SWT.RADIO); + timeframeModeButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + timeframeModeButton.setSelection(true); + + return composite; + } + + /** + * {@inheritDoc} + */ + public Map eventFired() { + Sensitivity sensitivity = Sensitivity.fromOrd(slider.getSelection()); + + Map preferenceControlMap = new HashMap(); + preferenceControlMap.put(PreferenceId.SamplingRate.SLIDER_ID, sensitivity); + + // get the actual selected divider mode button and set the specific id + if (timeframeModeButton.getSelection()) { + preferenceControlMap.put(PreferenceId.SamplingRate.DIVIDER_ID, PreferenceId.SamplingRate.TIMEFRAME_DIVIDER_ID); + } + + return preferenceControlMap; + } + + /** + * {@inheritDoc} + */ + public PreferenceId getControlGroupId() { + return CONTROL_GROUP_ID; + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/SamplingRateSelecterFactory.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/SamplingRateSelecterFactory.java new file mode 100644 index 000000000..9473ac99f --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/SamplingRateSelecterFactory.java @@ -0,0 +1,36 @@ +package info.novatec.inspectit.rcp.editor.preferences.control; + +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId.SamplingRate; +import info.novatec.inspectit.rcp.editor.preferences.control.samplingrate.SamplingRateMode; + +/** + * The factory for sampling rate mode selection. + * + * @author Eduard Tudenhoefner + * + */ +public final class SamplingRateSelecterFactory { + + /** + * The private constructor. + */ + private SamplingRateSelecterFactory() { + } + + /** + * Returns the {@link SamplingRateMode}. + * + * @param samplingRateIdEnum + * The {@link SamplingRate}. + * @return The {@link SamplingRateMode}. + */ + public static SamplingRateMode selectSamplingRateMode(SamplingRate samplingRateIdEnum) { + switch (samplingRateIdEnum) { + case TIMEFRAME_DIVIDER_ID: + return SamplingRateMode.TIMEFRAME_DIVIDER; + default: + return SamplingRateMode.TIMEFRAME_DIVIDER; + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/TimeLineControl.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/TimeLineControl.java new file mode 100644 index 000000000..1d59d1eab --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/TimeLineControl.java @@ -0,0 +1,235 @@ +package info.novatec.inspectit.rcp.editor.preferences.control; + +import info.novatec.inspectit.rcp.editor.preferences.IPreferenceGroup; +import info.novatec.inspectit.rcp.editor.preferences.IPreferencePanel; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.nebula.widgets.cdatetime.CDT; +import org.eclipse.nebula.widgets.cdatetime.CDateTime; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.events.HyperlinkAdapter; +import org.eclipse.ui.forms.events.HyperlinkEvent; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Hyperlink; +import org.eclipse.ui.forms.widgets.Section; + +/** + * The time line control for the views that has set of links for fast setting of the time-frame, as + * well as two date boxes for setting the time-frame directly. + * + * @author Ivan Senic + * + */ +public class TimeLineControl extends AbstractPreferenceControl implements IPreferenceControl, PreferenceEventCallback { + + /** + * From date widget. + */ + private CDateTime fromDateTime; + + /** + * To date widget. + */ + private CDateTime toDateTime; + + /** + * Old from date. + */ + private Date oldToDate; + + /** + * Old to date. + */ + private Date oldFromDate; + + /** + * Main composite in the preference control. + */ + private Composite mainComposite; + + /** + * Default constructor. + * + * @param preferencePanel + * Preference panel. + */ + public TimeLineControl(IPreferencePanel preferencePanel) { + super(preferencePanel); + } + + /** + * {@inheritDoc} + */ + @Override + public PreferenceId getControlGroupId() { + return PreferenceId.TIMELINE; + } + + /** + * {@inheritDoc} + */ + @Override + public Composite createControls(Composite parent, FormToolkit toolkit) { + Section section = toolkit.createSection(parent, Section.TITLE_BAR); + section.setText("Time Range"); + section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + mainComposite = toolkit.createComposite(section); + mainComposite.setLayout(new GridLayout(1, false)); + section.setClient(mainComposite); + + Composite linksComposite = toolkit.createComposite(mainComposite); + linksComposite.setLayout(new GridLayout(14, false)); + linksComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + + toolkit.createLabel(linksComposite, "Last: "); + createTimeHyperlink(linksComposite, toolkit, "15 minutes", 15 * 60 * 1000L); + toolkit.createLabel(linksComposite, " | "); + createTimeHyperlink(linksComposite, toolkit, "1 hour", 60 * 60 * 1000L); + toolkit.createLabel(linksComposite, " | "); + createTimeHyperlink(linksComposite, toolkit, "6 hours", 6 * 60 * 60 * 1000L); + toolkit.createLabel(linksComposite, " | "); + createTimeHyperlink(linksComposite, toolkit, "12 hours", 12 * 60 * 60 * 1000L); + toolkit.createLabel(linksComposite, " | "); + createTimeHyperlink(linksComposite, toolkit, "1 day", 24 * 60 * 60 * 1000L); + toolkit.createLabel(linksComposite, " | "); + createTimeHyperlink(linksComposite, toolkit, "7 days", 7 * 24 * 60 * 60 * 1000L); + toolkit.createLabel(linksComposite, " | "); + createTimeHyperlink(linksComposite, toolkit, "30 days", 30 * 24 * 60 * 60 * 1000L); + + Composite timeComposite = toolkit.createComposite(mainComposite); + timeComposite.setLayout(new GridLayout(4, false)); + timeComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + + Date toDate = new Date(); + Date fromDate = new Date(toDate.getTime() - PreferenceId.TimeLine.TIMELINE_DEFAULT); + + toolkit.createLabel(timeComposite, "From: "); + fromDateTime = new CDateTime(timeComposite, CDT.BORDER | CDT.DROP_DOWN); + fromDateTime.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + fromDateTime.setFormat(CDT.DATE_SHORT | CDT.TIME_SHORT); + fromDateTime.setSelection(fromDate); + + toolkit.createLabel(timeComposite, "To: "); + toDateTime = new CDateTime(timeComposite, CDT.BORDER | CDT.DROP_DOWN); + toDateTime.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + toDateTime.setFormat(CDT.DATE_SHORT | CDT.TIME_SHORT); + toDateTime.setSelection(toDate); + + SelectionListener selectionListener = new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + getPreferencePanel().update(); + } + }; + fromDateTime.addSelectionListener(selectionListener); + toDateTime.addSelectionListener(selectionListener); + + getPreferencePanel().registerCallback(this); + + return mainComposite; + } + + /** + * {@inheritDoc} + */ + @Override + public Map eventFired() { + Map preferenceControlMap = new HashMap(); + Date toDate = toDateTime.getSelection(); + Date fromDate = fromDateTime.getSelection(); + + if (null == oldToDate || oldToDate.getTime() != toDate.getTime()) { + preferenceControlMap.put(PreferenceId.TimeLine.TO_DATE_ID, toDate); + oldToDate = new Date(toDate.getTime()); + } + if (null == oldFromDate || oldFromDate.getTime() != fromDate.getTime()) { + preferenceControlMap.put(PreferenceId.TimeLine.FROM_DATE_ID, fromDate); + oldFromDate = new Date(fromDate.getTime()); + } + + return preferenceControlMap; + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + getPreferencePanel().removeCallback(this); + } + + /** + * Creates {@link Hyperlink} that when clicked sets the last specified time to the timeframe + * control. + * + * @param parent + * Parent composite. + * @param toolkit + * {@link FormToolkit} + * @param text + * Text on the {@link Hyperlink}. + * @param time + * Wanted time frame to set on click. + * @return Created {@link Hyperlink}. + */ + private Hyperlink createTimeHyperlink(Composite parent, FormToolkit toolkit, String text, final long time) { + Hyperlink hyperlink = toolkit.createHyperlink(parent, text, SWT.NONE); + hyperlink.addHyperlinkListener(new HyperlinkAdapter() { + @Override + public void linkActivated(HyperlinkEvent e) { + Date toDate = new Date(); + Date fromDate = new Date(toDate.getTime() - time); + toDateTime.setSelection(toDate); + fromDateTime.setSelection(fromDate); + getPreferencePanel().update(); + } + }); + return hyperlink; + } + + /** + * Sets control enabled or not. + * + * @param enabled + * If control is enabled or not. + */ + private void setEnabled(boolean enabled) { + fromDateTime.setEnabled(enabled); + toDateTime.setEnabled(enabled); + } + + /** + * {@inheritDoc} + *

+ * If live is set on we disable this preference. + */ + @Override + public void eventFired(PreferenceEvent preferenceEvent) { + if (PreferenceId.LIVEMODE.equals(preferenceEvent.getPreferenceId())) { + Boolean liveOn = (Boolean) preferenceEvent.getPreferenceMap().get(PreferenceId.LiveMode.BUTTON_LIVE_ID); + if (null != liveOn && liveOn.booleanValue()) { + setEnabled(false); + } else { + setEnabled(true); + } + } else if (PreferenceId.TIMELINE.equals(preferenceEvent.getPreferenceId())) { + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeLine.FROM_DATE_ID)) { + fromDateTime.setSelection((Date) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeLine.FROM_DATE_ID)); + } + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeLine.TO_DATE_ID)) { + toDateTime.setSelection((Date) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeLine.TO_DATE_ID)); + } + } + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/samplingrate/ISamplingRateMode.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/samplingrate/ISamplingRateMode.java new file mode 100644 index 000000000..1c1880d3b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/samplingrate/ISamplingRateMode.java @@ -0,0 +1,36 @@ +package info.novatec.inspectit.rcp.editor.preferences.control.samplingrate; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.indexing.aggregation.IAggregator; + +import java.util.Date; +import java.util.List; + +/** + * The interface for the sampling rate modes. + * + * @author Eduard Tudenhoefner + * + */ +public interface ISamplingRateMode { + + /** + * Adjusts the sampling rate with the given sampling rate mode and returns a {@link List} with + * the aggregated {@link DefaultData} objects. + * + * @param + * Type of element. + * @param defaultDataList + * The {@link List} with {@link DefaultData} objects. + * @param from + * The start time. + * @param to + * The end time. + * @param samplingRate + * The sampling rate. + * @param aggregator + * {@link IAggregator} to be used. + * @return A {@link List} with the aggregated data. + */ + List adjustSamplingRate(List defaultDataList, Date from, Date to, int samplingRate, IAggregator aggregator); +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/samplingrate/SamplingRateMode.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/samplingrate/SamplingRateMode.java new file mode 100644 index 000000000..f6943ed36 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/preferences/control/samplingrate/SamplingRateMode.java @@ -0,0 +1,132 @@ +package info.novatec.inspectit.rcp.editor.preferences.control.samplingrate; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.indexing.aggregation.IAggregator; +import info.novatec.inspectit.indexing.aggregation.impl.AggregationPerformer; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; + +/** + * The enumeration for sampling rate modes. + * + * @author Eduard Tudenhoefner + * + */ +public enum SamplingRateMode implements ISamplingRateMode { + /** + * The identifier of the sampling rate modes. + */ + TIMEFRAME_DIVIDER { + /** + * {@inheritDoc} + */ + @Override + public List adjustSamplingRate(List defaultDataList, Date from, Date to, int samplingRate, IAggregator aggregator) { + long timeframe = 0; + + if (samplingRate > 0 && defaultDataList != null) { + timeframe = (to.getTime() - from.getTime()) / samplingRate; + } else { + return defaultDataList; + } + + List resultList = new ArrayList(); + + // define the start and end position of the first time frame + long timeframeStartTime = Long.MAX_VALUE; + long timeframeEndTime = 0; + int fromIndex = 0; + int toIndex = -1; + + // find the starting value + for (int i = 0; i < defaultDataList.size(); i++) { + Date dataDate = defaultDataList.get(i).getTimeStamp(); + + // find first data object which lies in the specified time range + if ((dataDate.getTime() == from.getTime() || dataDate.after(from)) && dataDate.before(to)) { + fromIndex = i; + timeframeStartTime = dataDate.getTime() - timeframe / 2; + timeframeEndTime = dataDate.getTime() + timeframe / 2; + + if (i - 1 >= 0) { + // we add a data object so that the drawn graph does not start with the + // first drawn object, but the line will go out of the graph. + if (i - 1 > 0) { + // this data object is not the first of the list, thus we add the very + // first data object to the result list because of the auto range of + // jfreechart. Otherwise the graph would not scale correctly. + resultList.add(defaultDataList.get(0)); + } + resultList.add(defaultDataList.get(i - 1)); + } + + break; + } + } + + AggregationPerformer aggregationPerformer = new AggregationPerformer(aggregator); + // iterate over time frames + while (timeframeStartTime < to.getTime() + timeframe) { + long averageTime = (timeframeStartTime + timeframeEndTime) / 2; + + for (int i = fromIndex; i < defaultDataList.size(); i++) { + long dataTime = defaultDataList.get(i).getTimeStamp().getTime(); + + if (dataTime > timeframeEndTime) { + // if the actual data object is not anymore in the actual time frame, then + // the last data object was + toIndex = i - 1; + break; + } else if (i + 1 == defaultDataList.size()) { + // if end of list is reached then toIndex is the end of the list + toIndex = i; + } + } + + // aggregate data objects only when toIndex changed + if (toIndex >= 0 && fromIndex <= toIndex) { + // aggregate data and set the average time stamp + aggregationPerformer.reset(); + // aggregation performer does not include toIndex to the aggregation + aggregationPerformer.processList(defaultDataList, fromIndex, toIndex + 1); + List aggregatedData = aggregationPerformer.getResultList(); + if (CollectionUtils.isNotEmpty(aggregatedData)) { + E data = aggregatedData.get(0); + data.setTimeStamp(new Timestamp(averageTime)); + resultList.add(data); + } + + // set the fromIndex on the actual data object + fromIndex = toIndex + 1; + } + + // adjust timeframe + timeframeStartTime = timeframeEndTime; + timeframeEndTime += timeframe; + // reset the toIndex + toIndex = -1; + } + + if (0 != fromIndex) { + // we try to append an object at the right non-visible part of the graph so that a + // line is drawn. + if (defaultDataList.size() > fromIndex) { + resultList.add(defaultDataList.get(fromIndex)); + } + if (defaultDataList.size() > fromIndex + 1) { + // there are some objects untouched, thus we need to add the very last data + // object to the result list for the auto scaling of jfreechart to work. + resultList.add(defaultDataList.get(defaultDataList.size() - 1)); + } + } + + return resultList; + } + }; + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/root/AbstractRootEditor.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/root/AbstractRootEditor.java new file mode 100644 index 000000000..a8cfac642 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/root/AbstractRootEditor.java @@ -0,0 +1,702 @@ +package info.novatec.inspectit.rcp.editor.root; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.editor.ISubView; +import info.novatec.inspectit.rcp.editor.SubViewFactory; +import info.novatec.inspectit.rcp.editor.composite.AbstractCompositeSubView; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.IPreferencePanel; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId.LiveMode; +import info.novatec.inspectit.rcp.formatter.ImageFormatter; +import info.novatec.inspectit.rcp.provider.IInputDefinitionProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryChangeListener; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; +import info.novatec.inspectit.rcp.repository.StorageRepositoryDefinition; +import info.novatec.inspectit.rcp.storage.listener.StorageChangeListener; +import info.novatec.inspectit.storage.IStorageData; +import info.novatec.inspectit.storage.LocalStorageData; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.resource.ResourceManager; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorSite; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.part.EditorPart; + +/** + * The abstract root editor is the base class of all editors (or more specific the views). + * Currently, the only root editor existing is the {@link FormRootEditor}. An editor is used here, + * and not a view, because more than one editor can be opened more easily. Plus the + * {@link IEditorInput} interface makes it easy to define the input for the views. + *

+ * If the same editor is already opened (thus the editorinput is the same as one that is already + * opened), the view is switched to existing one. + * + * @author Patrice Bouillet + * + */ +public abstract class AbstractRootEditor extends EditorPart implements IRootEditor, IInputDefinitionProvider, CmrRepositoryChangeListener, StorageChangeListener { + + /** + * The inner class for the update timer which just calls the + * {@link AbstractRootEditor#refresh()} method in the {@link Display} thread. + * + * @author Patrice Bouillet + * + */ + private final class UpdateTimerTask extends TimerTask { + /** + * {@inheritDoc} + */ + @Override + public void run() { + Display.getDefault().asyncExec(new Runnable() { + public void run() { + try { + doRefresh(); + } catch (Exception e) { + InspectIT.getDefault().createErrorDialog("The update mechanism on the view failed!", e, -1); + } + } + }); + } + } + + /** + * The update timer used for the automatic update of the contained views. + */ + private Timer updateTimer; + + /** + * The {@link IPreferencePanel}. + */ + private IPreferencePanel preferencePanel; + + /** + * The contained subview. + */ + private ISubView subView; + + /** + * The active subview. + */ + private ISubView activeSubView; + + /** + * The selection change listener, initialized lazily; null if not yet created. + */ + private ISelectionChangedListener selectionChangedListener = null; + + /** + * The post selection changed listener. + */ + private ISelectionChangedListener postSelectionChangedListener = null; + + /** + * The selection object. Needed for comparison with the new one. + */ + private ISelection selection; + + /** + * Denotes if the active {@link ISubView} is currently maximized. + */ + private boolean isMaximizedMode = false; + + /** + * Resource manager. + */ + private ResourceManager resourceManager = new LocalResourceManager(JFaceResources.getResources()); + + /** + * {@inheritDoc} + */ + public void doRefresh() { + subView.doRefresh(); + } + + /** + * Returns the input definition for this view. + * + * @return The input definition. + */ + public InputDefinition getInputDefinition() { + InputDefinition inputDefinition = (InputDefinition) getEditorInput().getAdapter(InputDefinition.class); + Assert.isNotNull(inputDefinition); + return inputDefinition; + } + + /** + * Starts the update timer. + */ + protected void startUpdateTimer() { + if (null == updateTimer) { + updateTimer = new Timer(); + updateTimer.schedule(new UpdateTimerTask(), 0L, getInputDefinition().getUpdateRate()); + } + } + + /** + * Stops the update timer. + */ + protected void stopUpdateTimer() { + if (null != updateTimer) { + updateTimer.cancel(); + updateTimer = null; // NOPMD + } + } + + /** + * {@inheritDoc} + */ + @Override + public void doSave(IProgressMonitor monitor) { + } + + /** + * {@inheritDoc} + */ + @Override + public void doSaveAs() { + } + + /** + * {@inheritDoc} + */ + @Override + public void init(IEditorSite editorSite, IEditorInput editorInput) throws PartInitException { + // check for valid input + if (!(editorInput instanceof RootEditorInput)) { + throw new PartInitException("Invalid Input: Must be RootEditorInput"); + } + + // set site and input + setSite(editorSite); + setInput(editorInput); + setTitleImage(ImageFormatter.getOverlayedEditorImage(getInputDefinition().getEditorPropertiesData().getPartImage(), getInputDefinition().getRepositoryDefinition(), resourceManager)); + + this.subView = SubViewFactory.createSubView(getInputDefinition().getId()); + this.subView.setRootEditor(this); + this.subView.init(); + editorSite.setSelectionProvider(new MultiSubViewSelectionProvider(this)); + + InspectIT.getDefault().getCmrRepositoryManager().addCmrRepositoryChangeListener(this); + InspectIT.getDefault().getInspectITStorageManager().addStorageChangeListener(this); + } + + /** + * {@inheritDoc} + */ + @Override + public final void createPartControl(Composite parent) { + setPartName(getInputDefinition().getEditorPropertiesData().getPartName()); + setTitleToolTip(getInputDefinition().getEditorPropertiesData().getPartTooltip()); + + // fill the view with content. + createView(parent); + + // start the update timer if it is requested. + if (getInputDefinition().isAutomaticUpdate()) { + startUpdateTimer(); + } else { + // do an update one time + Timer timer = new Timer(); + timer.schedule(new UpdateTimerTask(), 0L); + } + + if (null != preferencePanel) { + PreferenceEventCallback callback = new PreferenceEventCallback() { + /** + * {@inheritDoc} + */ + public void eventFired(PreferenceEvent preferenceEvent) { + if (PreferenceId.LIVEMODE.equals(preferenceEvent.getPreferenceId())) { + boolean isLiveMode = false; + if (null != updateTimer) { + stopUpdateTimer(); + isLiveMode = true; + } + if (preferenceEvent.getPreferenceMap().containsKey(LiveMode.REFRESH_RATE)) { + long refresh = (Long) preferenceEvent.getPreferenceMap().get(LiveMode.REFRESH_RATE); + getInputDefinition().setUpdateRate(refresh); + } + + if (preferenceEvent.getPreferenceMap().containsKey(LiveMode.BUTTON_LIVE_ID)) { + isLiveMode = (Boolean) preferenceEvent.getPreferenceMap().get(LiveMode.BUTTON_LIVE_ID); + } + + if (isLiveMode) { + startUpdateTimer(); + } else { + stopUpdateTimer(); + } + } else if (PreferenceId.UPDATE.equals(preferenceEvent.getPreferenceId())) { + doRefresh(); + } + getSubView().preferenceEventFired(preferenceEvent); + } + }; + preferencePanel.registerCallback(callback); + } + } + + /** + * Create the view in the subclasses. + * + * @param parent + * The parent composite. + */ + protected abstract void createView(Composite parent); + + /** + * Sets the preference panel. Throws {@link NullPointerException} if + * preferencePanel is set to null. + * + * @param preferencePanel + * The preference panel. + */ + protected void setPreferencePanel(IPreferencePanel preferencePanel) { + Assert.isNotNull(preferencePanel); + + this.preferencePanel = preferencePanel; + } + + /** + * {@inheritDoc} + */ + public IPreferencePanel getPreferencePanel() { + return preferencePanel; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isDirty() { + // can never be true + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isSaveAsAllowed() { + // can never be true, nothing to save. + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void setFocus() { + if (null != getActiveSubView() && null != getActiveSubView().getControl()) { + getActiveSubView().getControl().setFocus(); + } else if (null != subView && null != subView.getControl()) { + subView.getControl().setFocus(); + } + } + + /** + * {@inheritDoc} + */ + public ISubView getSubView() { + return subView; + } + + /** + * {@inheritDoc} + */ + public void setActiveSubView(ISubView subView) { + Assert.isNotNull(subView); + + if (!ObjectUtils.equals(activeSubView, subView)) { + this.activeSubView = subView; + + ISelectionProvider selectionProvider = activeSubView.getSelectionProvider(); + if (selectionProvider != null) { + ISelectionProvider outerProvider = getSite().getSelectionProvider(); + if (outerProvider instanceof MultiSubViewSelectionProvider) { + ISelection newSelection = selectionProvider.getSelection(); + MultiSubViewSelectionProvider provider = (MultiSubViewSelectionProvider) outerProvider; + + if (ObjectUtils.equals(selection, newSelection)) { + // The selection object didn't change, but the selection area did, thus we + // have to simulate a little bit. Problem is due to TreeSelection#equals + // passing equal comparison to the parent class (which means that a + // treeselection and a structuredselection are equal if they refer to the + // same selection). See bug at + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=135837 + SelectionChangedEvent event = new SelectionChangedEvent(selectionProvider, StructuredSelection.EMPTY); + provider.fireSelectionChanged(event); + provider.firePostSelectionChanged(event); + } + // now the real event + selection = newSelection; + SelectionChangedEvent event = new SelectionChangedEvent(selectionProvider, selection); + provider.fireSelectionChanged(event); + provider.firePostSelectionChanged(event); + } + } + } + } + + /** + * {@inheritDoc} + */ + public ISubView getActiveSubView() { + return activeSubView; + } + + /** + * {@inheritDoc} + */ + public void setDataInput(List data) { + subView.setDataInput(data); + } + + /** + * Returns the selection changed listener which listens to the nested editor's selection + * changes, and calls handleSelectionChanged . + * + * @return the selection changed listener + */ + public ISelectionChangedListener getSelectionChangedListener() { + if (selectionChangedListener == null) { + selectionChangedListener = new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + AbstractRootEditor.this.handleSelectionChanged(event); + } + }; + } + return selectionChangedListener; + } + + /** + * Returns the post selection change listener which listens to the nested editor's selection + * changes. + * + * @return the post selection change listener. + */ + public ISelectionChangedListener getPostSelectionChangedListener() { + if (postSelectionChangedListener == null) { + postSelectionChangedListener = new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + AbstractRootEditor.this.handlePostSelectionChanged(event); + } + }; + } + return postSelectionChangedListener; + } + + /** + * Handles a selection changed event from the nested sub view. The default implementation gets + * the selection provider from the site, and calls fireSelectionChanged on it (only + * if it is an instance of MultiSubViewSelectionProvider), passing a new event + * object. + * + * @param event + * The event. + */ + protected void handleSelectionChanged(SelectionChangedEvent event) { + ISelectionProvider provider = getSite().getSelectionProvider(); + if (provider instanceof MultiSubViewSelectionProvider) { + SelectionChangedEvent newEvent = new SelectionChangedEvent(provider, event.getSelection()); + MultiSubViewSelectionProvider prov = (MultiSubViewSelectionProvider) provider; + + prov.fireSelectionChanged(newEvent); + } + } + + /** + * Handles a post selection changed even from the active sub view. + * + * @param event + * The event + */ + protected void handlePostSelectionChanged(SelectionChangedEvent event) { + ISelectionProvider provider = getSite().getSelectionProvider(); + if (provider instanceof MultiSubViewSelectionProvider) { + SelectionChangedEvent newEvent = new SelectionChangedEvent(provider, event.getSelection()); + MultiSubViewSelectionProvider prov = (MultiSubViewSelectionProvider) provider; + + prov.firePostSelectionChanged(newEvent); + } + } + + /** + * Sets the current selection object. + * + * @param selection + * the selection object to set. + */ + public void setSelection(ISelection selection) { + this.selection = selection; + } + + /** + * Returns if the currently active sub-view can be maximized. Note that only if a + * {@link AbstractCompositeSubView} is a main sub-view of the editor this action is possible. In + * addition to that, number of sub-view has to be larger of 1 and currently no view has to be + * maximized. + *

+ * If {@link #activeSubView} is null, this method will still return true if all the conditions + * above are fulfilled. Note that in this case, maximize action will maximize the first view. + * + * @return True if the maximize active sub-view action can be executed. + */ + public boolean canMaximizeActiveSubView() { + if (isMaximizedMode) { + return false; + } else { + if (subView instanceof AbstractCompositeSubView) { + AbstractCompositeSubView compositeSubView = (AbstractCompositeSubView) subView; + List allViews = compositeSubView.getSubViews(); + if (null != allViews && allViews.size() > 1) { + return true; + } + } + return false; + } + } + + /** + * Returns if the active sub view is currently maximized and thus can be minimized. + * + * @return Returns if the active sub view is currently maximized and thus can be minimized. + */ + public boolean canMinimizeActiveSubView() { + return isMaximizedMode; + } + + /** + * Maximizes the active sub-view if that is possible. + */ + public void maximizeActiveSubView() { + if (canMaximizeActiveSubView()) { + maximizeSubView((AbstractCompositeSubView) subView, activeSubView); + isMaximizedMode = true; + ISelectionProvider selectionProvider = getSite().getSelectionProvider(); + selectionProvider.setSelection(StructuredSelection.EMPTY); + } + } + + /** + * Recursively maximizes all needed sub-views until the active one is maximized. + * + * @param compositeSubView + * {@link AbstractCompositeSubView} to start from. + * @param view + * View to maximize. + * @return True if the composite sub view did the maximization. + */ + private boolean maximizeSubView(AbstractCompositeSubView compositeSubView, ISubView view) { + if (null == view) { + compositeSubView.maximizeSubView(null); + return true; + } + + if (compositeSubView.getSubViews().contains(view)) { + compositeSubView.maximizeSubView(view); + return true; + } else { + for (ISubView viewInComposite : compositeSubView.getSubViews()) { + if (viewInComposite instanceof AbstractCompositeSubView) { + boolean maximized = maximizeSubView((AbstractCompositeSubView) viewInComposite, view); + if (maximized) { + compositeSubView.maximizeSubView(viewInComposite); + return true; + } + } + } + } + return false; + } + + /** + * Minimizes the active sub-view if that is possible. + */ + public void minimizeActiveSubView() { + if (canMinimizeActiveSubView()) { + restoreMaximization((AbstractCompositeSubView) subView); + isMaximizedMode = false; + ISelectionProvider selectionProvider = getSite().getSelectionProvider(); + selectionProvider.setSelection(StructuredSelection.EMPTY); + } + } + + /** + * Recursively restores the maximized mode. + * + * @param compositeSubView + * {@link AbstractCompositeSubView} to start from. + */ + private void restoreMaximization(AbstractCompositeSubView compositeSubView) { + compositeSubView.restoreMaximization(); + for (ISubView viewInComposite : compositeSubView.getSubViews()) { + if (viewInComposite instanceof AbstractCompositeSubView) { + restoreMaximization((AbstractCompositeSubView) viewInComposite); + } + } + } + + /** + * Returns if the editor had the active sub-view maximized. + * + * @return Returns if the editor had the active sub-view maximized. + */ + public boolean isActiveViewMaximized() { + return isMaximizedMode; + } + + /** + * Updates the name of the editor. + * + * @param name + * New name. + */ + public void updateEditorName(String name) { + if (StringUtils.isNotEmpty(name)) { + setPartName(name); + } + } + + /** + * {@inheritDoc} + */ + public void repositoryOnlineStatusUpdated(CmrRepositoryDefinition repositoryDefinition, OnlineStatus oldStatus, OnlineStatus newStatus) { + } + + /** + * {@inheritDoc} + */ + public void repositoryAdded(CmrRepositoryDefinition cmrRepositoryDefinition) { + } + + /** + * {@inheritDoc} + */ + public void repositoryRemoved(CmrRepositoryDefinition cmrRepositoryDefinition) { + if (ObjectUtils.equals(cmrRepositoryDefinition, getInputDefinition().getRepositoryDefinition())) { + close(); + } else if (getInputDefinition().getRepositoryDefinition() instanceof StorageRepositoryDefinition) { + // close also if storage is displayed from the repository that is removed + StorageRepositoryDefinition storageRepositoryDefinition = (StorageRepositoryDefinition) getInputDefinition().getRepositoryDefinition(); + if (ObjectUtils.equals(cmrRepositoryDefinition, storageRepositoryDefinition.getCmrRepositoryDefinition()) && !storageRepositoryDefinition.getLocalStorageData().isFullyDownloaded()) { + close(); + } + } + } + + /** + * {@inheritDoc} + */ + public void repositoryDataUpdated(CmrRepositoryDefinition cmrRepositoryDefinition) { + } + + /** + * {@inheritDoc} + */ + public void repositoryAgentDeleted(CmrRepositoryDefinition cmrRepositoryDefinition, PlatformIdent agent) { + if (ObjectUtils.equals(cmrRepositoryDefinition, getInputDefinition().getRepositoryDefinition())) { + if (agent.getId() == getInputDefinition().getIdDefinition().getPlatformId()) { + close(); + } + } + } + + /** + * {@inheritDoc} + */ + public void storageDataUpdated(IStorageData storageData) { + } + + /** + * {@inheritDoc} + */ + public void storageRemotelyDeleted(IStorageData storageData) { + RepositoryDefinition repositoryDefinition = getInputDefinition().getRepositoryDefinition(); + if (repositoryDefinition instanceof StorageRepositoryDefinition) { + LocalStorageData localStorageData = ((StorageRepositoryDefinition) repositoryDefinition).getLocalStorageData(); + if (!localStorageData.isFullyDownloaded() && ObjectUtils.equals(localStorageData.getId(), storageData.getId())) { + close(); + } + } + } + + /** + * {@inheritDoc} + */ + public void storageLocallyDeleted(IStorageData storageData) { + RepositoryDefinition repositoryDefinition = getInputDefinition().getRepositoryDefinition(); + if (repositoryDefinition instanceof StorageRepositoryDefinition) { + LocalStorageData localStorageData = ((StorageRepositoryDefinition) repositoryDefinition).getLocalStorageData(); + if (ObjectUtils.equals(localStorageData.getId(), storageData.getId())) { + if (!InspectIT.getDefault().getInspectITStorageManager().getMountedAvailableStorages().contains(storageData)) { + // close only if the remote one is also not available + close(); + } + } + } + } + + /** + * Closes the editor. + */ + protected void close() { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + getEditorSite().getPage().closeEditor(AbstractRootEditor.this, false); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + InspectIT.getDefault().getCmrRepositoryManager().removeCmrRepositoryChangeListener(this); + InspectIT.getDefault().getInspectITStorageManager().removeStorageChangeListener(this); + + // stop the timer if it is active + stopUpdateTimer(); + + // dispose the local resource manager + resourceManager.dispose(); + + super.dispose(); + + // dispose the preference panel + if (null != preferencePanel) { + preferencePanel.dispose(); + } + + if (null != subView) { + subView.dispose(); + } + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/root/FormRootEditor.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/root/FormRootEditor.java new file mode 100644 index 000000000..b0a39da61 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/root/FormRootEditor.java @@ -0,0 +1,128 @@ +package info.novatec.inspectit.rcp.editor.root; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.composite.BreadcrumbTitleComposite; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData; +import info.novatec.inspectit.rcp.editor.preferences.FormPreferencePanel; +import info.novatec.inspectit.rcp.editor.preferences.IPreferencePanel; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.Objects; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.Form; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * An implementation of a root editor which uses a form to create a nicer view. + * + * @author Patrice Bouillet + * + */ +public class FormRootEditor extends AbstractRootEditor { + + /** + * The identifier of the {@link FormRootEditor}. + */ + public static final String ID = "inspectit.editor.formrooteditor"; + + /** + * The form toolkit which defines the colors etc. + */ + private FormToolkit toolkit; + + /** + * The form of the view. + */ + private Form form; + + /** + * {@link BreadcrumbTitleComposite}. + */ + private BreadcrumbTitleComposite breadcrumbTitleComposite; + + /** + * {@inheritDoc} + */ + @Override + public void createView(Composite parent) { + // create the toolkit + this.toolkit = new FormToolkit(parent.getDisplay()); + + // create the preference panel with the callback + IPreferencePanel preferencePanel = new FormPreferencePanel(toolkit); + // set the preference panel + setPreferencePanel(preferencePanel); + + // create the form + form = toolkit.createForm(parent); + form.getBody().setLayout(new GridLayout()); + // decorate the heading to make it look better + toolkit.decorateFormHeading(form); + + // create breadcrumb composite + RepositoryDefinition repositoryDefinition = getInputDefinition().getRepositoryDefinition(); + PlatformIdent platformIdent = repositoryDefinition.getCachedDataService().getPlatformIdentForId(getInputDefinition().getIdDefinition().getPlatformId()); + breadcrumbTitleComposite = new BreadcrumbTitleComposite(form.getHead(), SWT.NONE); + breadcrumbTitleComposite.setRepositoryDefinition(repositoryDefinition); + breadcrumbTitleComposite.setAgent(TextFormatter.getAgentDescription(platformIdent), InspectIT.getDefault().getImage(InspectITImages.IMG_AGENT)); + breadcrumbTitleComposite.setGroup(getInputDefinition().getEditorPropertiesData().getSensorName(), getInputDefinition().getEditorPropertiesData().getSensorImage()); + breadcrumbTitleComposite.setView(getInputDefinition().getEditorPropertiesData().getViewName(), getInputDefinition().getEditorPropertiesData().getViewImage()); + form.setHeadClient(breadcrumbTitleComposite); + + // create an preference area if the subviews are requesting it + preferencePanel.createPartControl(form.getBody(), getSubView().getPreferenceIds(), getInputDefinition(), breadcrumbTitleComposite.getToolBarManager()); + + // go further with creating the subview(s) + getSubView().createPartControl(form.getBody(), toolkit); + getSubView().getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + super.dispose(); + // dispose of the toolkit + toolkit.dispose(); + breadcrumbTitleComposite.dispose(); + } + + /** + * @return the form + */ + public Form getForm() { + return form; + } + + /** + * Gets {@link #breadcrumbTitleComposite}. + * + * @return {@link #breadcrumbTitleComposite} + */ + public BreadcrumbTitleComposite getBreadcrumbTitleComposite() { + return breadcrumbTitleComposite; + } + + /** + * {@inheritDoc} + */ + @Override + public void updateEditorName(String name) { + EditorPropertiesData editorPropertiesData = getInputDefinition().getEditorPropertiesData(); + if (Objects.equals(editorPropertiesData.getPartName(), editorPropertiesData.getSensorName())) { + breadcrumbTitleComposite.setGroup(name, editorPropertiesData.getSensorImage()); + } else { + breadcrumbTitleComposite.setView(name, editorPropertiesData.getViewImage()); + } + super.updateEditorName(name); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/root/IRootEditor.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/root/IRootEditor.java new file mode 100644 index 000000000..006f339a7 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/root/IRootEditor.java @@ -0,0 +1,71 @@ +package info.novatec.inspectit.rcp.editor.root; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.editor.ISubView; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.IPreferencePanel; + +import java.util.List; + +/** + * Interface for all root editors. + * + * @author Patrice Bouillet + * + */ +public interface IRootEditor { + + /** + * Refresh the view. + */ + void doRefresh(); + + /** + * Returns the sub view registered for this root editor. + * + * @return the sub view. + */ + ISubView getSubView(); + + /** + * Sets the current active sub view. + * + * @param subView + * The sub view. + */ + void setActiveSubView(ISubView subView); + + /** + * Returns the current active sub view. One editor can only have one sub view child, but this + * child can act as a composite with other sub views contained in it. So this method returns the + * sub view which is currently active somewhere in one of the composite sub-view child elements + * (if there are any). + * + * @return The active sub view. + */ + ISubView getActiveSubView(); + + /** + * Returns the input definition for this view. + * + * @return The input definition. + */ + InputDefinition getInputDefinition(); + + /** + * This will set the data input of the view. Every view can initialize itself with some data + * (like live data from the server). This is only needed if some specific needs to be displayed. + * + * @param data + * The list of {@link DefaultData} objects. + */ + void setDataInput(List data); + + /** + * Returns the preference panel. + * + * @return The preference panel. + */ + IPreferencePanel getPreferencePanel(); + +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/root/MultiSubViewSelectionProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/root/MultiSubViewSelectionProvider.java new file mode 100644 index 000000000..79ef82706 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/root/MultiSubViewSelectionProvider.java @@ -0,0 +1,148 @@ +package info.novatec.inspectit.rcp.editor.root; + +import info.novatec.inspectit.rcp.editor.ISubView; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.core.runtime.SafeRunner; +import org.eclipse.jface.util.SafeRunnable; +import org.eclipse.jface.viewers.IPostSelectionProvider; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; + +/** + * @author Patrice Bouillet + * + */ +public class MultiSubViewSelectionProvider implements IPostSelectionProvider { + + /** + * Registered selection changed listeners (element type: ISelectionChangedListener + * ). + */ + private ListenerList listeners = new ListenerList(); + + /** + * Registered post selection changed listeners. + */ + private ListenerList postListeners = new ListenerList(); + + /** + * The root editor. + */ + private AbstractRootEditor rootEditor; + + /** + * Constructor needs a root editor. + * + * @param rootEditor + * The root editor. + */ + public MultiSubViewSelectionProvider(AbstractRootEditor rootEditor) { + Assert.isNotNull(rootEditor); + + this.rootEditor = rootEditor; + } + + /** + * {@inheritDoc} + */ + public void addPostSelectionChangedListener(ISelectionChangedListener listener) { + postListeners.add(listener); + } + + /** + * {@inheritDoc} + */ + public void removePostSelectionChangedListener(ISelectionChangedListener listener) { + postListeners.remove(listener); + } + + /** + * {@inheritDoc} + */ + public void addSelectionChangedListener(ISelectionChangedListener listener) { + listeners.add(listener); + } + + /** + * {@inheritDoc} + */ + public void removeSelectionChangedListener(ISelectionChangedListener listener) { + listeners.remove(listener); + } + + /** + * {@inheritDoc} + */ + public ISelection getSelection() { + ISubView subView = rootEditor.getActiveSubView(); + if (null != subView) { + ISelectionProvider selectionProvider = subView.getSelectionProvider(); + if (null != selectionProvider) { + return selectionProvider.getSelection(); + } + } + + return StructuredSelection.EMPTY; + } + + /** + * {@inheritDoc} + */ + public void setSelection(ISelection selection) { + ISubView subView = rootEditor.getActiveSubView(); + if (null != subView) { + ISelectionProvider selectionProvider = subView.getSelectionProvider(); + if (null != selectionProvider) { + selectionProvider.setSelection(selection); + } + } + } + + /** + * Notifies all registered selection changed listeners that the editor's selection has changed. + * Only listeners registered at the time this method is called are notified. + * + * @param event + * the selection changed event + */ + public void fireSelectionChanged(final SelectionChangedEvent event) { + Object[] listeners = this.listeners.getListeners(); + fireEventChange(event, listeners); + } + + /** + * Notifies all post selection changed listeners that the editor's selection has changed. + * + * @param event + * the event to propagate. + */ + public void firePostSelectionChanged(final SelectionChangedEvent event) { + Object[] listeners = postListeners.getListeners(); + fireEventChange(event, listeners); + } + + /** + * Fires the actual event. + * + * @param event + * The event to fire. + * @param listeners + * All the registered listeners. + */ + private void fireEventChange(final SelectionChangedEvent event, Object[] listeners) { + for (int i = 0; i < listeners.length; ++i) { + final ISelectionChangedListener l = (ISelectionChangedListener) listeners[i]; + SafeRunner.run(new SafeRunnable() { + public void run() { + l.selectionChanged(event); + } + }); + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/root/RootEditorInput.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/root/RootEditorInput.java new file mode 100644 index 000000000..853c39ef6 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/root/RootEditorInput.java @@ -0,0 +1,119 @@ +package info.novatec.inspectit.rcp.editor.root; + +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IPersistableElement; + +/** + * This editor input is used for all views and can only be set in the composite view controller as + * this is the only one which can be set with an input. + * + * @author Patrice Bouillet + * + */ +public class RootEditorInput implements IEditorInput { + + /** + * The input definition which holds everything a view needs to create the content. + */ + private InputDefinition inputDefinition; + + /** + * Only constructor which needs an input definition. + * + * @param inputDefinition + * The input definition. + */ + public RootEditorInput(InputDefinition inputDefinition) { + Assert.isNotNull(inputDefinition); + + this.inputDefinition = inputDefinition; + } + + /** + * {@inheritDoc} + */ + public boolean exists() { + return false; + } + + /** + * {@inheritDoc} + */ + public ImageDescriptor getImageDescriptor() { + return ImageDescriptor.getMissingImageDescriptor(); + } + + /** + * {@inheritDoc} + */ + public String getName() { + return inputDefinition.toString(); + } + + /** + * {@inheritDoc} + */ + public IPersistableElement getPersistable() { + return null; + } + + /** + * {@inheritDoc} + */ + public String getToolTipText() { + return inputDefinition.getEditorPropertiesData().getPartTooltip(); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("rawtypes") + public Object getAdapter(Class adapter) { + if (InputDefinition.class == adapter) { + return this.inputDefinition; + } + + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((inputDefinition == null) ? 0 : inputDefinition.hashCode()); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + RootEditorInput other = (RootEditorInput) obj; + if (inputDefinition == null) { + if (other.inputDefinition != null) { + return false; + } + } else if (!inputDefinition.equals(other.inputDefinition)) { + return false; + } + return true; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/root/SubViewClassificationController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/root/SubViewClassificationController.java new file mode 100644 index 000000000..40d9aa40f --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/root/SubViewClassificationController.java @@ -0,0 +1,38 @@ +package info.novatec.inspectit.rcp.editor.root; + +/** + * Interface for defining the classification of sub views, so that different actions can be + * performed with differently classified views. + * + * @author Ivan Senic + * + */ +public interface SubViewClassificationController { + + /** + * Defines different classification options for the view. + * + * @author Ivan Senic + * + */ + public enum SubViewClassification { + + /** + * In master view input is controlled by its input controller. + */ + MASTER, + + /** + * In slave view input is controlled by some other view/input controller. + */ + SLAVE; + } + + /** + * + * @return Returns the sub view classification. + * @see SubViewClassification + */ + SubViewClassification getSubViewClassification(); + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/search/ISearchExecutor.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/ISearchExecutor.java new file mode 100644 index 000000000..c7c4c6adf --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/ISearchExecutor.java @@ -0,0 +1,41 @@ +package info.novatec.inspectit.rcp.editor.search; + +import info.novatec.inspectit.rcp.editor.search.criteria.SearchCriteria; +import info.novatec.inspectit.rcp.editor.search.criteria.SearchResult; + +/** + * Interface for components that can execute the search. + * + * @author Ivan Senic + * + */ +public interface ISearchExecutor { + + /** + * Executes the search. + * + * @param searchCriteria + * {@link SearchCriteria} + * @return {@link SearchResult} after the action. + */ + SearchResult executeSearch(SearchCriteria searchCriteria); + + /** + * Executes show next element. + * + * @return {@link SearchResult} after the action. + */ + SearchResult next(); + + /** + * Executes show next element. + * + * @return {@link SearchResult} after the action. + */ + SearchResult previous(); + + /** + * Signals that all search related changes should be cleared. + */ + void clearSearch(); +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/search/OpenedSearchControlCache.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/OpenedSearchControlCache.java new file mode 100644 index 000000000..5c4595f6f --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/OpenedSearchControlCache.java @@ -0,0 +1,72 @@ +package info.novatec.inspectit.rcp.editor.search; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Caches the information about the {@link ISearchExecutor}s that already have {@link SearchControl} + * opened. + * + * @author Ivan Senic + * + */ +public final class OpenedSearchControlCache { + + /** + * Private constructor. + */ + private OpenedSearchControlCache() { + } + + /** + * Map for caching. + */ + private static Map openedSearchControlMap = new ConcurrentHashMap(); + + /** + * Returns if the {@link ISearchExecutor} already has a {@link SearchControl} registered. + * + * @param searchExecutor + * {@link ISearchExecutor} to check. + * @return True if the {@link SearchControl} is already registered for a {@link ISearchExecutor} + * . + */ + public static boolean hasSearchControlOpened(ISearchExecutor searchExecutor) { + return openedSearchControlMap.containsKey(searchExecutor); + } + + /** + * Registers the search control with search executor. + * + * @param searchExecutor + * {@link ISearchExecutor} + * @param searchControl + * {@link SearchControl} + */ + public static void register(ISearchExecutor searchExecutor, SearchControl searchControl) { + if (!hasSearchControlOpened(searchExecutor)) { + openedSearchControlMap.put(searchExecutor, searchControl); + } + } + + /** + * Registers the search executor. + * + * @param searchExecutor + * {@link ISearchExecutor} + */ + public static void unregister(ISearchExecutor searchExecutor) { + openedSearchControlMap.remove(searchExecutor); + } + + /** + * Returns the search control. + * + * @param searchExecutor + * Search executor control is bounded to. + * @return {@link SearchControl}. + */ + public static SearchControl getSearchControl(ISearchExecutor searchExecutor) { + return openedSearchControlMap.get(searchExecutor); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/search/SearchControl.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/SearchControl.java new file mode 100644 index 000000000..55c07a6a2 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/SearchControl.java @@ -0,0 +1,366 @@ +package info.novatec.inspectit.rcp.editor.search; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.search.criteria.SearchCriteria; +import info.novatec.inspectit.rcp.editor.search.criteria.SearchResult; +import info.novatec.inspectit.util.ObjectUtils; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.FontMetrics; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IPartListener; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.forms.FormColors; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * Control that is displayed for search purposes. + * + * @author Ivan Senic + * + */ +public class SearchControl { + + /** + * {@link info.novatec.inspectit.rcp.editor.ISubView} to notify about search. + */ + private ISearchExecutor searchExecutor; + + /** + * Main composite for showing the components. + */ + private Composite mainComposite; + + /** + * Search text box. + */ + private Text searchTextBox; + + /** + * Is case sensitive. + */ + private ToolItem caseSensitiveButton; + + /** + * Close button. + */ + private ToolItem closeButton; + + /** + * Shell that we will create to display the search. + */ + private Shell shell; + + /** + * Next button. + */ + private ToolItem next; + + /** + * Previous button. + */ + private ToolItem previous; + + /** + * Last {@link SearchResult}. + */ + private SearchResult lastSearchResult; + + /** + * Default constructor. + * + * @param searchExecutor + * The implementation that will be pass the search string. + * @param parentShell + * Shell where the search will be created. + * @param paintRelativeControl + * Control where the paint of the search box should occur. + * @param editor + * Editor where the search will be painted. This is needed because of the editor + * closing, hiding, etc action. + */ + public SearchControl(ISearchExecutor searchExecutor, Shell parentShell, Control paintRelativeControl, IEditorPart editor) { + this.searchExecutor = searchExecutor; + createSearchShell(parentShell, paintRelativeControl, editor); + } + + /** + * Creates the control. The control will be painted in the top-right corner of the + * controlToPaint. + * + * @param parentShell + * Parent shell. + * @param paintRelativeControl + * Control where the paint of the search box should occur. + * @param editor + * Editor where the search will be painted. This is needed because of the editor + * closing, hiding, etc action. + */ + private void createSearchShell(Shell parentShell, final Control paintRelativeControl, final IEditorPart editor) { + Display display = Display.getDefault(); + FormColors formColors = new FormColors(display); + FormToolkit toolkit = new FormToolkit(formColors); + shell = new Shell(parentShell, SWT.BORDER | SWT.TOOL); + + mainComposite = toolkit.createComposite(shell); + GridLayout layout = new GridLayout(3, false); + mainComposite.setLayout(layout); + mainComposite.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false)); + + searchTextBox = toolkit.createText(mainComposite, null, SWT.BORDER); + GridData gd = new GridData(); + gd.grabExcessHorizontalSpace = true; + gd.horizontalAlignment = GridData.FILL; + gd.minimumWidth = 200; + + searchTextBox.setLayoutData(gd); + searchTextBox.addTraverseListener(new TraverseListener() { + @Override + public void keyTraversed(TraverseEvent e) { + if (e.detail == SWT.TRAVERSE_RETURN) { + executeSearch(); + } + } + }); + searchTextBox.addPaintListener(new PaintListener() { + @Override + public void paintControl(PaintEvent e) { + if (null != lastSearchResult) { + String string = lastSearchResult.getCurrentOccurence() + " of " + lastSearchResult.getTotalOccurrences(); + if (lastSearchResult.getTotalOccurrences() > 0) { + paintString(e, string, Display.getDefault().getSystemColor(SWT.COLOR_GRAY)); + } else { + paintString(e, string, Display.getDefault().getSystemColor(SWT.COLOR_RED)); + } + } + } + + private void paintString(PaintEvent e, String string, Color color) { + Point point = searchTextBox.getSize(); + + FontMetrics fontMetrics = e.gc.getFontMetrics(); + int width = fontMetrics.getAverageCharWidth() * string.length(); + int height = fontMetrics.getHeight(); + e.gc.setForeground(color); + e.gc.drawString(string, point.x - width - searchTextBox.getBorderWidth() - 2, (point.y - height - searchTextBox.getBorderWidth() * 2) / 2, true); + } + }); + + ToolBar toolBar = new ToolBar(mainComposite, SWT.FLAT); + toolBar.setBackground(formColors.getBackground()); + + previous = new ToolItem(toolBar, SWT.PUSH | SWT.NO_BACKGROUND); + previous.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_PREVIOUS)); + previous.setEnabled(false); + previous.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + executePrevious(); + } + }); + + next = new ToolItem(toolBar, SWT.PUSH | SWT.NO_BACKGROUND); + next.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_NEXT)); + next.setEnabled(false); + next.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + executeNext(); + } + }); + + caseSensitiveButton = new ToolItem(toolBar, SWT.CHECK | SWT.NO_BACKGROUND); + caseSensitiveButton.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_FONT)); + caseSensitiveButton.setToolTipText("Case sensitive"); + caseSensitiveButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + executeSearch(); + } + }); + + // added additional composite to the right, so that minimizing and maximizing the window + // can look better + Composite helpComposite = toolkit.createComposite(mainComposite); + gd = new GridData(); + gd.grabExcessHorizontalSpace = true; + gd.horizontalAlignment = GridData.FILL; + gd.minimumWidth = 0; + gd.heightHint = 0; + gd.widthHint = 0; + helpComposite.setLayoutData(gd); + + closeButton = new ToolItem(toolBar, SWT.PUSH | SWT.NO_BACKGROUND); + closeButton.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_CLOSE)); + closeButton.setToolTipText("Close"); + closeButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + searchExecutor.clearSearch(); + closeControl(); + } + }); + + KeyAdapter keyCloseAdapter = new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.keyCode == SWT.ESC) { + searchExecutor.clearSearch(); + closeControl(); + } + } + }; + + mainComposite.addKeyListener(keyCloseAdapter); + for (Control child : mainComposite.getChildren()) { + child.addKeyListener(keyCloseAdapter); + } + + searchTextBox.forceFocus(); + + mainComposite.pack(); + mainComposite.setSize(mainComposite.computeSize(SWT.DEFAULT, SWT.DEFAULT)); + final Point shellSize = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT); + Point controlPosition = paintRelativeControl.toDisplay(0, 0); + Point controlSize = paintRelativeControl.getSize(); + + int xPosition = controlPosition.x + controlSize.x - shellSize.x - paintRelativeControl.getBorderWidth(); + int yPosition = controlPosition.y + controlSize.y - shellSize.y - paintRelativeControl.getBorderWidth(); + + shell.setLocation(xPosition, yPosition); + shell.setSize(shellSize); + shell.open(); + + paintRelativeControl.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + if (!shell.isDisposed()) { + Point controlPosition = paintRelativeControl.toDisplay(0, 0); + Point controlSize = paintRelativeControl.getSize(); + int xPosition = controlPosition.x + controlSize.x - shellSize.x - paintRelativeControl.getBorderWidth(); + int yPosition = controlPosition.y + controlSize.y - shellSize.y - paintRelativeControl.getBorderWidth(); + + shell.setLocation(xPosition, yPosition); + } + } + }); + + editor.getSite().getPage().addPartListener(new IPartListener() { + @Override + public void partOpened(IWorkbenchPart part) { + } + + @Override + public void partDeactivated(IWorkbenchPart part) { + if (ObjectUtils.equals(editor, part)) { + closeControl(); + } + } + + @Override + public void partClosed(IWorkbenchPart part) { + if (ObjectUtils.equals(editor, part)) { + closeControl(); + } + } + + @Override + public void partBroughtToTop(IWorkbenchPart part) { + } + + @Override + public void partActivated(IWorkbenchPart part) { + } + }); + + OpenedSearchControlCache.register(searchExecutor, this); + shell.addShellListener(new ShellAdapter() { + @Override + public void shellClosed(ShellEvent e) { + searchExecutor.clearSearch(); + OpenedSearchControlCache.unregister(searchExecutor); + } + }); + + toolkit.dispose(); + } + + /** + * Closes the control. + */ + public final void closeControl() { + if (!shell.isDisposed()) { + shell.close(); + } + } + + /** + * Executes the search. + */ + private void executeSearch() { + String searchString = searchTextBox.getText().trim(); + if (!searchString.isEmpty()) { + SearchCriteria searchCriteria = new SearchCriteria(searchString, caseSensitiveButton.getSelection()); + lastSearchResult = searchExecutor.executeSearch(searchCriteria); + processSearchResult(lastSearchResult); + } else { + searchExecutor.clearSearch(); + lastSearchResult = null; // NOPMD + searchTextBox.redraw(); + } + } + + /** + * Executes next functionality. + */ + private void executeNext() { + lastSearchResult = searchExecutor.next(); + processSearchResult(lastSearchResult); + } + + /** + * Execute previous functionality. + */ + private void executePrevious() { + lastSearchResult = searchExecutor.previous(); + processSearchResult(lastSearchResult); + } + + /** + * Processes the {@link SearchResult}. + * + * @param result + * {@link SearchResult}. + */ + private void processSearchResult(SearchResult result) { + searchTextBox.redraw(); + if (null != result) { + next.setEnabled(result.isCanShowNext()); + previous.setEnabled(result.isCanShowPrevious()); + } + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/search/criteria/SearchCriteria.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/criteria/SearchCriteria.java new file mode 100644 index 000000000..0ee4b0353 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/criteria/SearchCriteria.java @@ -0,0 +1,143 @@ +package info.novatec.inspectit.rcp.editor.search.criteria; + +import org.eclipse.core.runtime.Assert; + +/** + * POJO that holds the information about a search. + * + * @author Ivan Senic + * + */ +public class SearchCriteria { + + /** + * String to be search for. + */ + private String searchString = ""; + + /** + * Upper case of searched string. + */ + private String searchStringUpperCase = ""; + + /** + * If search is case sensitive. + */ + private boolean caseSensitive; + + /** + * Default constructor. + * + * @param searchString + * String to search. + */ + public SearchCriteria(String searchString) { + this(searchString, false); + } + + /** + * Secondary constructor. + * + * @param searchString + * String to search. + * @param caseSensitive + * Should search be case sensitive. + */ + public SearchCriteria(String searchString, boolean caseSensitive) { + Assert.isNotNull(searchString); + this.caseSensitive = caseSensitive; + this.searchString = searchString; + this.searchStringUpperCase = searchString.toUpperCase(); + } + + /** + * @return the searchString + */ + public String getSearchString() { + return searchString; + } + + /** + * @param searchString + * the searchString to set + */ + public void setSearchString(String searchString) { + this.searchString = searchString; + if (null != searchString) { + searchStringUpperCase = searchString.toUpperCase(); + } else { + searchStringUpperCase = null; // NOPMD + } + } + + /** + * @return the searcgStringUpperCase + */ + public String getSearchStringUpperCase() { + return searchStringUpperCase; + } + + /** + * @return the caseSensitive + */ + public boolean isCaseSensitive() { + return caseSensitive; + } + + /** + * @param caseSensitive + * the caseSensitive to set + */ + public void setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (caseSensitive ? 1231 : 1237); + result = prime * result + ((searchString == null) ? 0 : searchString.hashCode()); + result = prime * result + ((searchStringUpperCase == null) ? 0 : searchStringUpperCase.hashCode()); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + SearchCriteria other = (SearchCriteria) obj; + if (caseSensitive != other.caseSensitive) { + return false; + } + if (searchString == null) { + if (other.searchString != null) { + return false; + } + } else if (!searchString.equals(other.searchString)) { + return false; + } + if (searchStringUpperCase == null) { + if (other.searchStringUpperCase != null) { + return false; + } + } else if (!searchStringUpperCase.equals(other.searchStringUpperCase)) { + return false; + } + return true; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/search/criteria/SearchResult.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/criteria/SearchResult.java new file mode 100644 index 000000000..b1eb06f5a --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/criteria/SearchResult.java @@ -0,0 +1,171 @@ +package info.novatec.inspectit.rcp.editor.search.criteria; + +/** + * Class holding the search result. + * + * @author Ivan Senic + * + */ +public class SearchResult { + + /** + * Total occurrences found. + */ + private int totalOccurrences; + + /** + * Current occurrence displayed. + */ + private int currentOccurence; + + /** + * Can next element be shown. + */ + private boolean canShowNext; + + /** + * Can previous element be shown. + */ + private boolean canShowPrevious; + + /** + * Default constructor. + * + * @param currentOccurence + * Current occurrence displayed. + * @param totalOccurrences + * Total occurrences found. + * @param canShowNext + * Can next element be shown. + * @param canShowPrevious + * Can previous element be shown. + */ + public SearchResult(int currentOccurence, int totalOccurrences, boolean canShowNext, boolean canShowPrevious) { + super(); + this.currentOccurence = currentOccurence; + this.totalOccurrences = totalOccurrences; + this.canShowNext = canShowNext; + this.canShowPrevious = canShowPrevious; + } + + /** + * Gets {@link #totalOccurrences}. + * + * @return {@link #totalOccurrences} + */ + public int getTotalOccurrences() { + return totalOccurrences; + } + + /** + * Sets {@link #totalOccurrences}. + * + * @param totalOccurrences + * New value for {@link #totalOccurrences} + */ + public void setTotalOccurrences(int totalOccurrences) { + this.totalOccurrences = totalOccurrences; + } + + /** + * Gets {@link #currentOccurence}. + * + * @return {@link #currentOccurence} + */ + public int getCurrentOccurence() { + return currentOccurence; + } + + /** + * Sets {@link #currentOccurence}. + * + * @param currentOccurence + * New value for {@link #currentOccurence} + */ + public void setCurrentOccurence(int currentOccurence) { + this.currentOccurence = currentOccurence; + } + + /** + * Gets {@link #canShowNext}. + * + * @return {@link #canShowNext} + */ + public boolean isCanShowNext() { + return canShowNext; + } + + /** + * Sets {@link #canShowNext}. + * + * @param canShowNext + * New value for {@link #canShowNext} + */ + public void setCanShowNext(boolean canShowNext) { + this.canShowNext = canShowNext; + } + + /** + * Gets {@link #canShowPrevious}. + * + * @return {@link #canShowPrevious} + */ + public boolean isCanShowPrevious() { + return canShowPrevious; + } + + /** + * Sets {@link #canShowPrevious}. + * + * @param canShowPrevious + * New value for {@link #canShowPrevious} + */ + public void setCanShowPrevious(boolean canShowPrevious) { + this.canShowPrevious = canShowPrevious; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (canShowNext ? 1231 : 1237); + result = prime * result + (canShowPrevious ? 1231 : 1237); + result = prime * result + currentOccurence; + result = prime * result + totalOccurrences; + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + SearchResult other = (SearchResult) obj; + if (canShowNext != other.canShowNext) { + return false; + } + if (canShowPrevious != other.canShowPrevious) { + return false; + } + if (currentOccurence != other.currentOccurence) { + return false; + } + if (totalOccurrences != other.totalOccurrences) { + return false; + } + return true; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/search/factory/SearchFactory.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/factory/SearchFactory.java new file mode 100644 index 000000000..c30f48a08 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/factory/SearchFactory.java @@ -0,0 +1,339 @@ +package info.novatec.inspectit.rcp.editor.search.factory; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.communication.data.AggregatedExceptionSensorData; +import info.novatec.inspectit.communication.data.AggregatedHttpTimerData; +import info.novatec.inspectit.communication.data.AggregatedSqlStatementData; +import info.novatec.inspectit.communication.data.AggregatedTimerData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.editor.search.criteria.SearchCriteria; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.Map; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; + +/** + * Class for supporting the search functionality. + * + * @author Ivan Senic + * + */ +public final class SearchFactory { + + /** + * Search finder for {@link TimerData}. + */ + private static TimerSearchFinder timerSearchFinder = new TimerSearchFinder(); + + /** + * Search finder for {@link SqlStatementData}. + */ + private static SqlSearchFinder sqlSearchFinder = new SqlSearchFinder(); + + /** + * Search finder for {@link HttpTimerData}. + */ + private static HttpSearchFinder httpSearchFinder = new HttpSearchFinder(); + + /** + * Search finder for {@link ExceptionSearchFinder}. + */ + private static ExceptionSearchFinder exceptionSearchFinder = new ExceptionSearchFinder(); + + /** + * Search finder for {@link InvocationSearchFinder}. + */ + private static InvocationSearchFinder invocationSearchFinder = new InvocationSearchFinder(); + + /** + * Private constructor. + */ + private SearchFactory() { + } + + /** + * Returns if the element is search compatible. + * + * @param element + * Element to check. + * @param searchCriteria + * Criteria. + * @param repositoryDefinition + * {@link RepositoryDefinition} where the element can be found. + * @return True if element is compatible with search. False otherwise. False also if the element + * to check is null. + * + */ + public static boolean isSearchCompatible(Object element, SearchCriteria searchCriteria, RepositoryDefinition repositoryDefinition) { + if (null == element) { + return false; + } + + if (TimerData.class.equals(element.getClass()) || AggregatedTimerData.class.equals(element.getClass())) { + return timerSearchFinder.isSearchCompatible((TimerData) element, searchCriteria, repositoryDefinition); + } else if (SqlStatementData.class.equals(element.getClass()) || AggregatedSqlStatementData.class.equals(element.getClass())) { + return sqlSearchFinder.isSearchCompatible((SqlStatementData) element, searchCriteria, repositoryDefinition); + } else if (HttpTimerData.class.equals(element.getClass()) || AggregatedHttpTimerData.class.equals(element.getClass())) { + return httpSearchFinder.isSearchCompatible((HttpTimerData) element, searchCriteria, repositoryDefinition); + } else if (ExceptionSensorData.class.equals(element.getClass()) || AggregatedExceptionSensorData.class.equals(element.getClass())) { + return exceptionSearchFinder.isSearchCompatible((ExceptionSensorData) element, searchCriteria, repositoryDefinition); + } else if (InvocationSequenceData.class.equals(element.getClass())) { + return invocationSearchFinder.isSearchCompatible((InvocationSequenceData) element, searchCriteria, repositoryDefinition); + } + return false; + } + + /** + * Abstract class for all Search finders. + * + * @author Ivan Senic + * + * @param + * Type of element finder can search + */ + private abstract static class AbstractSearchFinder { + + /** + * Returns if the element is search compatible. + * + * @param element + * Element to check. + * @param searchCriteria + * Criteria. + * @param repositoryDefinition + * {@link RepositoryDefinition} where the element can be found. + * @return True if element is compatible with search. + * + */ + public abstract boolean isSearchCompatible(E element, SearchCriteria searchCriteria, RepositoryDefinition repositoryDefinition); + + /** + * Null safe checks if the string is matching the {@link SearchCriteria}. + * + * @param str + * String to check. + * @param searchCriteria + * {@link SearchCriteria}. + * @return True only if the provided string to check is not null and it matches the + * {@link SearchCriteria}. + */ + protected boolean stringMatches(String str, SearchCriteria searchCriteria) { + if (str != null) { + if (!searchCriteria.isCaseSensitive()) { + return str.toUpperCase().indexOf(searchCriteria.getSearchStringUpperCase()) != -1; + } else { + return str.indexOf(searchCriteria.getSearchString()) != -1; + } + } + return false; + } + + /** + * Returns if the {@link MethodIdent} object is search compatible. Sub-classes can use this + * method. + * + * @param methodIdent + * {@link MethodIdent} to check. + * @param searchCriteria + * {@link SearchCriteria}. + * @return True if {@link MethodIdent} matches the {@link SearchCriteria}. + */ + protected boolean isSearchCompatible(MethodIdent methodIdent, SearchCriteria searchCriteria) { + if (null == methodIdent) { + return false; + } else if (stringMatches(methodIdent.getFQN(), searchCriteria)) { + return true; + } else if (stringMatches(methodIdent.getMethodName(), searchCriteria)) { + return true; + } else if (methodIdent.getParameters() != null && !methodIdent.getParameters().isEmpty()) { + for (String parameter : methodIdent.getParameters()) { + if (stringMatches(parameter, searchCriteria)) { + return true; + } + } + } + return false; + } + } + + /** + * Search finder for {@link TimerData}. + * + * @author Ivan Senic + * + */ + private static class TimerSearchFinder extends AbstractSearchFinder { + + /** + * {@inheritDoc} + */ + @Override + public boolean isSearchCompatible(TimerData element, SearchCriteria searchCriteria, RepositoryDefinition repositoryDefinition) { + MethodIdent methodIdent = repositoryDefinition.getCachedDataService().getMethodIdentForId(element.getMethodIdent()); + return super.isSearchCompatible(methodIdent, searchCriteria); + } + } + + /** + * Search finder for {@link SqlStatementData}. + * + * @author Ivan Senic + * + */ + private static class SqlSearchFinder extends AbstractSearchFinder { + + /** + * {@inheritDoc} + */ + @Override + public boolean isSearchCompatible(SqlStatementData element, SearchCriteria searchCriteria, RepositoryDefinition repositoryDefinition) { + if (stringMatches(element.getSql(), searchCriteria)) { + return true; + } else if (CollectionUtils.isNotEmpty(element.getParameterValues())) { + for (String param : element.getParameterValues()) { + if (stringMatches(param, searchCriteria)) { + return true; + } + } + } else if (stringMatches(element.getDatabaseProductName(), searchCriteria)) { + return true; + } else if (stringMatches(element.getDatabaseProductVersion(), searchCriteria)) { + return true; + } else if (stringMatches(element.getDatabaseUrl(), searchCriteria)) { + return true; + } + MethodIdent methodIdent = repositoryDefinition.getCachedDataService().getMethodIdentForId(element.getMethodIdent()); + return super.isSearchCompatible(methodIdent, searchCriteria); + } + } + + /** + * Search finder for {@link HttpTimerData}. + * + * @author Ivan Senic + * + */ + private static class HttpSearchFinder extends AbstractSearchFinder { + + @Override + public boolean isSearchCompatible(HttpTimerData element, SearchCriteria searchCriteria, RepositoryDefinition repositoryDefinition) { + if (stringMatches(element.getInspectItTaggingHeaderValue(), searchCriteria)) { + return true; + } else if (stringMatches(element.getRequestMethod(), searchCriteria)) { + return true; + } else if (stringMatches(element.getUri(), searchCriteria)) { + return true; + } else { + if (MapUtils.isNotEmpty(element.getAttributes())) { + + for (Map.Entry entry : element.getAttributes().entrySet()) { + if (stringMatches(entry.getKey(), searchCriteria) || stringMatches(entry.getValue(), searchCriteria)) { + return true; + } + } + } + if (MapUtils.isNotEmpty(element.getHeaders())) { + for (Map.Entry entry : element.getHeaders().entrySet()) { + if (stringMatches(entry.getKey(), searchCriteria) || stringMatches(entry.getValue(), searchCriteria)) { + return true; + } + } + } + if (MapUtils.isNotEmpty(element.getParameters())) { + for (Map.Entry entry : element.getParameters().entrySet()) { + if (stringMatches(entry.getKey(), searchCriteria)) { + return true; + } else { + for (String string : entry.getValue()) { + if (stringMatches(string, searchCriteria)) { + return true; + } + } + } + } + } + if (MapUtils.isNotEmpty(element.getSessionAttributes())) { + for (Map.Entry entry : element.getSessionAttributes().entrySet()) { + if (stringMatches(entry.getKey(), searchCriteria) || stringMatches(entry.getValue().toString(), searchCriteria)) { + return true; + } + } + } + } + MethodIdent methodIdent = repositoryDefinition.getCachedDataService().getMethodIdentForId(element.getMethodIdent()); + return super.isSearchCompatible(methodIdent, searchCriteria); + } + + } + + /** + * Search finder for {@link ExceptionSensorData}. + * + * @author Ivan Senic + * + */ + private static class ExceptionSearchFinder extends AbstractSearchFinder { + + /** + * {@inheritDoc} + */ + @Override + public boolean isSearchCompatible(ExceptionSensorData element, SearchCriteria searchCriteria, RepositoryDefinition repositoryDefinition) { + if (stringMatches(element.getCause(), searchCriteria)) { + return true; + } else if (stringMatches(element.getErrorMessage(), searchCriteria)) { + return true; + } else if (stringMatches(element.getStackTrace(), searchCriteria)) { + return true; + } else if (stringMatches(element.getThrowableType(), searchCriteria)) { + return true; + } + MethodIdent methodIdent = repositoryDefinition.getCachedDataService().getMethodIdentForId(element.getMethodIdent()); + return super.isSearchCompatible(methodIdent, searchCriteria); + } + + } + + /** + * Search finder for {@link InvocationSequenceData}. + * + * @author Ivan Senic + * + */ + private static class InvocationSearchFinder extends AbstractSearchFinder { + + /** + * {@inheritDoc} + */ + @Override + public boolean isSearchCompatible(InvocationSequenceData element, SearchCriteria searchCriteria, RepositoryDefinition repositoryDefinition) { + if (null != element.getTimerData()) { + if (SearchFactory.isSearchCompatible(element.getTimerData(), searchCriteria, repositoryDefinition)) { + return true; + } + } + if (null != element.getSqlStatementData()) { + if (SearchFactory.isSearchCompatible(element.getSqlStatementData(), searchCriteria, repositoryDefinition)) { + return true; + } + } + if (null != element.getExceptionSensorDataObjects() && !element.getExceptionSensorDataObjects().isEmpty()) { + for (ExceptionSensorData exData : element.getExceptionSensorDataObjects()) { + if (SearchFactory.isSearchCompatible(exData, searchCriteria, repositoryDefinition)) { + return true; + } + } + } + MethodIdent methodIdent = repositoryDefinition.getCachedDataService().getMethodIdentForId(element.getMethodIdent()); + return super.isSearchCompatible(methodIdent, searchCriteria); + } + + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/search/helper/AbstractSearchHelper.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/helper/AbstractSearchHelper.java new file mode 100644 index 000000000..d501697f0 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/helper/AbstractSearchHelper.java @@ -0,0 +1,327 @@ +package info.novatec.inspectit.rcp.editor.search.helper; + +import info.novatec.inspectit.rcp.editor.search.ISearchExecutor; +import info.novatec.inspectit.rcp.editor.search.criteria.SearchCriteria; +import info.novatec.inspectit.rcp.editor.search.criteria.SearchResult; +import info.novatec.inspectit.rcp.editor.search.factory.SearchFactory; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.eclipse.jface.viewers.StructuredViewer; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.swt.events.SelectionAdapter; + +/** + * Abstract search helper. Joins the search logics and delegates specific actions to the + * sub-classes. + * + * @author Ivan Senic + * + */ +public abstract class AbstractSearchHelper implements ISearchExecutor { + + /** + * Current occurrence. + */ + private int currentOccurrence; + + /** + * Total occurrences. + */ + private int totalOccurrences; + + /** + * Last used {@link SearchCriteria}. + */ + private SearchCriteria lastSearchCriteria; + + /** + * All objects that should be searched. + */ + private Object[] allObjects; + + /** + * List of found objects by {@link #lastSearchCriteria}. + */ + private List foundObjects = Collections.emptyList(); + + /** + * {@link RepositoryDefinition}. Needed for {@link SearchFactory}. + */ + private final RepositoryDefinition repositoryDefinition; + + /** + * Caching of the viewer's input hash. When the hash changes, we need to reload all objects to + * search. + */ + private int oldInputHash; + + /** + * The selection adapter that will clear the search on the new sorting of columns. + */ + private SelectionAdapter columnSortingListener = new SelectionAdapter() { + public void widgetSelected(org.eclipse.swt.events.SelectionEvent e) { + clearSearch(); + }; + }; + + /** + * If true singles that the {@link #clearSearch()} was executed and search should + * start over. + */ + private boolean cleared; + + /** + * @param repositoryDefinition + * {@link RepositoryDefinition}. Needed for {@link SearchFactory}. + */ + public AbstractSearchHelper(RepositoryDefinition repositoryDefinition) { + super(); + this.repositoryDefinition = repositoryDefinition; + } + + /** + * Performs the selection of the element. The sub-classes need to implement this method, so that + * the correct selection by viewer type is performed. + * + * @param element + * Element to select. + */ + public abstract void selectElement(Object element); + + /** + * Returns all objects that should be searched. + * + * @return Returns all objects that should be searched. + */ + public abstract Object[] getAllObjects(); + + /** + * Returns the viewer the search is performed on. This is necessary for input change checking + * and filtering. + * + * @return Returns the viewer the search is performed on. This is necessary for input change + * checking and filtering. + */ + public abstract StructuredViewer getViewer(); + + /** + * {@inheritDoc} + */ + @Override + public SearchResult executeSearch(SearchCriteria searchCriteria) { + if (!cleared && ObjectUtils.equals(lastSearchCriteria, searchCriteria)) { + // we search with same criteria as last time + // just execute next + return this.next(); + } else { + // we search with new criteria + if (!checkInput()) { + loadAllObjects(); + } + updateFoundObjects(searchCriteria); + if (totalOccurrences > 0) { + currentOccurrence = 1; + displayOccurence(currentOccurrence); + } else { + currentOccurrence = 0; + } + } + + lastSearchCriteria = searchCriteria; + cleared = false; + return getSearchResult(); + } + + /** + * {@inheritDoc} + */ + @Override + public SearchResult next() { + if (cleared) { + return executeSearch(lastSearchCriteria); + } + + if (!checkInput()) { + loadAllObjects(); + updateFoundObjects(lastSearchCriteria); + } else { + sortAsInViewer(foundObjects); + } + if (totalOccurrences > 1) { + currentOccurrence++; + if (currentOccurrence > totalOccurrences) { + currentOccurrence = 1; + } + displayOccurence(currentOccurrence); + } else { + currentOccurrence = totalOccurrences; + if (currentOccurrence > 0) { + displayOccurence(currentOccurrence); + } + } + + return getSearchResult(); + } + + /** + * {@inheritDoc} + */ + @Override + public SearchResult previous() { + if (cleared) { + return executeSearch(lastSearchCriteria); + } + + if (!checkInput()) { + loadAllObjects(); + updateFoundObjects(lastSearchCriteria); + } else { + sortAsInViewer(foundObjects); + } + if (totalOccurrences > 1) { + currentOccurrence--; + if (currentOccurrence == 0) { + currentOccurrence = totalOccurrences; + } + displayOccurence(currentOccurrence); + } else { + currentOccurrence = totalOccurrences; + if (currentOccurrence > 0) { + displayOccurence(currentOccurrence); + } + } + + return getSearchResult(); + } + + /** + * {@inheritDoc} + */ + @Override + public void clearSearch() { + cleared = true; + } + + /** + * + * @return Returns the search result by examining the state of the {@link #currentOccurrence} + * and {@link #totalOccurrences} values. + */ + private SearchResult getSearchResult() { + if (totalOccurrences > 1) { + return new SearchResult(currentOccurrence, totalOccurrences, true, true); + } else { + return new SearchResult(currentOccurrence, totalOccurrences, false, false); + } + } + + /** + * Checks if the input of the viewer changed since the last search. If so, the {@link #input} + * variable will be updated. + * + * @return True if the input did not change, false otherwise. + */ + private boolean checkInput() { + int inputHash = Arrays.hashCode(getAllObjects()); + if (oldInputHash != inputHash) { + oldInputHash = inputHash; + return false; + } else { + oldInputHash = inputHash; + return true; + } + } + + /** + * Loads all objects that need to be searched. + */ + private void loadAllObjects() { + allObjects = getAllObjects(); + } + + /** + * Updates the {@link #foundObjects} with given {@link SearchCriteria} against + * {@link #allObjects}. This method also checks if the filters of the viewer are satisfied. + * + * @param searchCriteria + * {@link SearchCriteria}. + */ + private void updateFoundObjects(SearchCriteria searchCriteria) { + foundObjects = new ArrayList(); + for (Object object : allObjects) { + if (SearchFactory.isSearchCompatible(object, searchCriteria, repositoryDefinition) && areFiltersPassed(object, getViewer().getFilters())) { + foundObjects.add(object); + } + } + sortAsInViewer(foundObjects); + totalOccurrences = foundObjects.size(); + } + + /** + * Sorts the list of objects as they appear in the table. + * + * @param objects + * List of objects. + */ + private void sortAsInViewer(List objects) { + if (null != getViewer().getComparator()) { + Collections.sort(objects, new Comparator() { + @Override + public int compare(Object o1, Object o2) { + return getViewer().getComparator().compare(getViewer(), o1, o2); + } + }); + } + } + + /** + * Are all filers passed. + * + * @param object + * Object to check. + * @param filters + * Filters. + * @return True if all filters are passed or no filter was given. + */ + private boolean areFiltersPassed(Object object, ViewerFilter[] filters) { + if (null != filters) { + for (ViewerFilter filter : filters) { + if (!filter.select(getViewer(), null, object)) { + return false; + } + } + } + return true; + } + + /** + * Displays the wanted occurrence. + * + * @param occurrence + * Occurrence to display. + */ + private void displayOccurence(int occurrence) { + if (totalOccurrences >= occurrence && foundObjects.size() >= occurrence) { + // select this element + Object selected = foundObjects.get(occurrence - 1); + this.selectElement(selected); + } + } + + /** + * Gets {@link #columnSortingListener}. + * + * @return {@link #columnSortingListener} + */ + protected SelectionAdapter getColumnSortingListener() { + return columnSortingListener; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/search/helper/DeferredTreeViewerSearchHelper.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/helper/DeferredTreeViewerSearchHelper.java new file mode 100644 index 000000000..346635dbf --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/helper/DeferredTreeViewerSearchHelper.java @@ -0,0 +1,73 @@ +package info.novatec.inspectit.rcp.editor.search.helper; + +import info.novatec.inspectit.rcp.editor.tree.DeferredTreeViewer; +import info.novatec.inspectit.rcp.editor.tree.input.TreeInputController; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import org.eclipse.jface.viewers.StructuredViewer; +import org.eclipse.swt.widgets.TreeColumn; + +/** + * Search helper for {@link DeferredTreeViewer}. + * + * @author Ivan Senic + * @see AbstractSearchHelper + * + */ +public class DeferredTreeViewerSearchHelper extends AbstractSearchHelper { + + /** + * {@link DeferredTreeViewer}. + */ + private final DeferredTreeViewer treeViewer; + + /** + * {@link TreeInputController}. + */ + private final TreeInputController treeInputController; + + /** + * Default constructor. + * + * @param treeViewer + * {@link DeferredTreeViewer}. + * @param treeInputController + * {@link TreeInputController}. + * @param repositoryDefinition + * {@link RepositoryDefinition}. Needed for + * {@link info.novatec.inspectit.rcp.editor.search.factory.SearchFactory}. + */ + public DeferredTreeViewerSearchHelper(DeferredTreeViewer treeViewer, TreeInputController treeInputController, RepositoryDefinition repositoryDefinition) { + super(repositoryDefinition); + this.treeViewer = treeViewer; + this.treeInputController = treeInputController; + for (TreeColumn treeColumn : treeViewer.getTree().getColumns()) { + treeColumn.addSelectionListener(getColumnSortingListener()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void selectElement(Object element) { + treeViewer.expandToObjectAndSelect(element, 0); + } + + /** + * {@inheritDoc} + */ + @Override + public Object[] getAllObjects() { + return treeInputController.getObjectsToSearch(treeViewer.getInput()); + } + + /** + * {@inheritDoc} + */ + @Override + public StructuredViewer getViewer() { + return treeViewer; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/search/helper/TableViewerSearchHelper.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/helper/TableViewerSearchHelper.java new file mode 100644 index 000000000..94f24b720 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/search/helper/TableViewerSearchHelper.java @@ -0,0 +1,72 @@ +package info.novatec.inspectit.rcp.editor.search.helper; + +import info.novatec.inspectit.rcp.editor.table.input.TableInputController; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.StructuredViewer; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.widgets.TableColumn; + +/** + * Search helper for {@link TableViewer}. + * + * @author Ivan Senic + * @see AbstractSearchHelper + * + */ +public class TableViewerSearchHelper extends AbstractSearchHelper { + + /** + * {@link TableViewer}. + */ + private final TableViewer tableViewer; + + /** + * {@link TableInputController}. + */ + private final TableInputController tableInputController; + + /** + * @param tableViewer + * {@link TableViewer}. + * @param tableInputController + * {@link TableInputController}. + * @param repositoryDefinition + * {@link RepositoryDefinition}. Needed for + * {@link info.novatec.inspectit.rcp.editor.search.factory.SearchFactory}. + */ + public TableViewerSearchHelper(TableViewer tableViewer, TableInputController tableInputController, RepositoryDefinition repositoryDefinition) { + super(repositoryDefinition); + this.tableViewer = tableViewer; + this.tableInputController = tableInputController; + for (TableColumn tableColumn : tableViewer.getTable().getColumns()) { + tableColumn.addSelectionListener(getColumnSortingListener()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void selectElement(Object element) { + StructuredSelection ss = new StructuredSelection(element); + tableViewer.setSelection(ss, true); + } + + /** + * {@inheritDoc} + */ + @Override + public Object[] getAllObjects() { + return tableInputController.getObjectsToSearch(tableViewer.getInput()); + } + + /** + * {@inheritDoc} + */ + @Override + public StructuredViewer getViewer() { + return tableViewer; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/RemoteTableViewerComparator.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/RemoteTableViewerComparator.java new file mode 100644 index 000000000..734c1823a --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/RemoteTableViewerComparator.java @@ -0,0 +1,85 @@ +package info.novatec.inspectit.rcp.editor.table; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.editor.viewers.AbstractViewerComparator; + +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.custom.BusyIndicator; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; + +/** + * Abstract class for all table views that need remote sorting. Implementing classes should + * implement method {@link #sortRemotely(ResultComparator)} which will be called when the sorting + * column is selected. In this method implementing classes should refresh the result. + * + * @author Ivan Senic + * + * @param + * Type to compare on. + */ +public abstract class RemoteTableViewerComparator extends AbstractViewerComparator { + + /** + * Adds a column to this comparator so it can be used to sort by. + * + * @param column + * The {@link TableColumn} implementation. comparatorProvider The id of the + * {@link TableColumn} (user-defined). + * @param comparator + * Comparator that will be used for the given column. + */ + public final void addColumn(final TableColumn column, final ResultComparator comparator) { + column.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + toggleSortColumn(comparator); + final SortState sortState = getSortState(); + + Table table = column.getParent(); + table.setSortColumn(column); + table.setSortDirection(sortState.getSwtDirection()); + + try { + BusyIndicator.showWhile(Display.getDefault(), new Runnable() { + @Override + public void run() { + if (sortState != SortState.NONE) { + sortRemotely(comparator); + } else { + sortRemotely(null); + } + } + }); + } catch (Exception exception) { + InspectIT.getDefault().createErrorDialog("Exception occurred trying to remotely sort on the selected column.", exception, -1); + } + } + }); + } + + /** + * Implementing classes should call the remote service and refresh the input of the table by + * using the given {@link ResultComparator}. Progress can be reported to given monitor. + * + * @param resultComparator + * Result comparator that should be used in the remote call. null can + * also be passed, meaning that no sorting or default sorting should be used. + */ + protected abstract void sortRemotely(ResultComparator resultComparator); + + /** + * {@inheritDoc} + *

+ * No sorting on the UI. + */ + @Override + public int compare(Viewer viewer, Object o1, Object o2) { + return 0; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/TableSubView.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/TableSubView.java new file mode 100644 index 000000000..6a37e69b8 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/TableSubView.java @@ -0,0 +1,494 @@ +package info.novatec.inspectit.rcp.editor.table; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.editor.AbstractSubView; +import info.novatec.inspectit.rcp.editor.ISubView; +import info.novatec.inspectit.rcp.editor.preferences.IPreferenceGroup; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.root.FormRootEditor; +import info.novatec.inspectit.rcp.editor.root.SubViewClassificationController.SubViewClassification; +import info.novatec.inspectit.rcp.editor.search.ISearchExecutor; +import info.novatec.inspectit.rcp.editor.search.criteria.SearchCriteria; +import info.novatec.inspectit.rcp.editor.search.criteria.SearchResult; +import info.novatec.inspectit.rcp.editor.search.helper.TableViewerSearchHelper; +import info.novatec.inspectit.rcp.editor.table.input.TableInputController; +import info.novatec.inspectit.rcp.editor.tooltip.ColumnAwareToolTipSupport; +import info.novatec.inspectit.rcp.editor.tooltip.IColumnToolTipProvider; +import info.novatec.inspectit.rcp.editor.viewers.CheckedDelegatingIndexLabelProvider; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.handlers.ShowHideColumnsHandler; +import info.novatec.inspectit.rcp.menu.ShowHideMenuManager; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * Sub-view which is used to create a table. + * + * @author Patrice Bouillet + * + */ +public class TableSubView extends AbstractSubView implements ISearchExecutor { + + /** + * The referenced input controller. + */ + private final TableInputController tableInputController; + + /** + * The created table viewer. + */ + private TableViewer tableViewer; + + /** + * Defines if a job is currently already executing. + */ + private volatile boolean jobInSchedule = false; + + /** + * {@link TableViewerSearchHelper}. + */ + private TableViewerSearchHelper tableViewerSearchHelper; + + /** + * Default constructor which needs a tree input controller to create all the content etc. + * + * @param tableInputController + * The table input controller. + */ + public TableSubView(TableInputController tableInputController) { + Assert.isNotNull(tableInputController); + + this.tableInputController = tableInputController; + } + + /** + * {@inheritDoc} + */ + @Override + public void init() { + tableInputController.setInputDefinition(getRootEditor().getInputDefinition()); + } + + /** + * {@inheritDoc} + */ + public void createPartControl(Composite parent, FormToolkit toolkit) { + // the style can not be SWT.VIRTUAL when the SWT.CHECK style is used + // the check of the elements won't work because of the virtual loading + int style = SWT.MULTI | SWT.FULL_SELECTION | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL; + if (tableInputController.isCheckStyle()) { + style |= SWT.CHECK; + } else { + style |= SWT.VIRTUAL; + } + final Table table = toolkit.createTable(parent, style); + table.setHeaderVisible(true); + table.setLinesVisible(true); + + tableViewer = new TableViewer(table); + + if (tableInputController.isCheckStyle()) { + TableViewerColumn viewerColumn = new TableViewerColumn(tableViewer, SWT.NONE); + viewerColumn.getColumn().setMoveable(false); + viewerColumn.getColumn().setResizable(true); + viewerColumn.getColumn().setWidth(30); + viewerColumn.getColumn().setText("Selected"); + } + + tableInputController.createColumns(tableViewer); + tableViewer.setUseHashlookup(true); + tableViewer.setContentProvider(tableInputController.getContentProvider()); + IBaseLabelProvider labelProvider = tableInputController.getLabelProvider(); + if (tableInputController.isCheckStyle() && labelProvider instanceof StyledCellIndexLabelProvider) { + labelProvider = new CheckedDelegatingIndexLabelProvider((StyledCellIndexLabelProvider) labelProvider); + } + tableViewer.setLabelProvider(labelProvider); + if (labelProvider instanceof IColumnToolTipProvider) { + ColumnAwareToolTipSupport.enableFor(tableViewer); + } + tableViewer.addDoubleClickListener(new IDoubleClickListener() { + public void doubleClick(DoubleClickEvent event) { + tableInputController.doubleClick(event); + } + }); + tableViewer.setComparator(tableInputController.getComparator()); + if (null != tableViewer.getComparator()) { + TableColumn[] tableColumns = tableViewer.getTable().getColumns(); + for (TableColumn tableColumn : tableColumns) { + tableColumn.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + tableViewer.refresh(); + } + }); + } + } + + // add show hide columns support + MenuManager headerMenuManager = new ShowHideMenuManager(tableViewer, tableInputController.getClass()); + headerMenuManager.setRemoveAllWhenShown(false); + + // normal selection menu manager + MenuManager selectionMenuManager = new MenuManager(); + selectionMenuManager.setRemoveAllWhenShown(true); + getRootEditor().getSite().registerContextMenu(FormRootEditor.ID + ".tablesubview", selectionMenuManager, tableViewer); + + final Menu selectionMenu = selectionMenuManager.createContextMenu(table); + final Menu headerMenu = headerMenuManager.createContextMenu(table); + + table.addListener(SWT.MenuDetect, new Listener() { + @Override + public void handleEvent(Event event) { + Point pt = Display.getDefault().map(null, table, new Point(event.x, event.y)); + Rectangle clientArea = table.getClientArea(); + boolean header = clientArea.y <= pt.y && pt.y < (clientArea.y + table.getHeaderHeight()); + if (header) { + table.setMenu(headerMenu); + } else { + table.setMenu(selectionMenu); + } + } + }); + + /** + * IMPORTANT: Only the menu set in the setMenu() will be disposed automatically. + */ + table.addListener(SWT.Dispose, new Listener() { + @Override + public void handleEvent(Event event) { + if (!headerMenu.isDisposed()) { + headerMenu.dispose(); + } + if (!selectionMenu.isDisposed()) { + selectionMenu.dispose(); + } + } + }); + + Object input = tableInputController.getTableInput(); + tableViewer.setInput(input); + + ControlAdapter columnResizeListener = new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + if (e.widget instanceof TableColumn) { + TableColumn column = (TableColumn) e.widget; + if (column.getWidth() > 0) { + ShowHideColumnsHandler.registerNewColumnWidth(tableInputController.getClass(), column.getText(), column.getWidth()); + } + } + } + + @Override + public void controlMoved(ControlEvent e) { + ShowHideColumnsHandler.setColumnOrder(tableInputController.getClass(), tableViewer.getTable().getColumnOrder()); + } + }; + + for (TableColumn column : table.getColumns()) { + if (tableInputController.canAlterColumnWidth(column)) { + Integer rememberedWidth = ShowHideColumnsHandler.getRememberedColumnWidth(tableInputController.getClass(), column.getText()); + boolean isColumnHidden = ShowHideColumnsHandler.isColumnHidden(tableInputController.getClass(), column.getText()); + + if (rememberedWidth != null && !isColumnHidden) { + column.setWidth(rememberedWidth.intValue()); + column.setResizable(true); + } else if (isColumnHidden) { + column.setWidth(0); + column.setResizable(false); + } + } + + column.addControlListener(columnResizeListener); + } + + // update the order of columns if the order was defined for the class, and no new columns + // were added + int[] columnOrder = ShowHideColumnsHandler.getColumnOrder(tableInputController.getClass()); + if (null != columnOrder && columnOrder.length == table.getColumns().length) { + table.setColumnOrder(columnOrder); + } else if (null != columnOrder) { + // if the order exists, but length is not same, then update with the default order + ShowHideColumnsHandler.setColumnOrder(tableInputController.getClass(), table.getColumnOrder()); + } + + tableViewerSearchHelper = new TableViewerSearchHelper(tableViewer, tableInputController, getRootEditor().getInputDefinition().getRepositoryDefinition()); + // add listener for the check box style if active + if (tableInputController.isCheckStyle()) { + table.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + if (e.detail == SWT.CHECK) { + if (e.item instanceof TableItem) { + TableItem item = (TableItem) e.item; + tableInputController.objectChecked(item.getData(), item.getChecked()); + } + } + } + }); + } + } + + /** + * {@inheritDoc} + */ + public void doRefresh() { + if (!jobInSchedule) { + jobInSchedule = true; + + Job job = new Job(getDataLoadingJobName()) { + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + tableInputController.doRefresh(monitor, getRootEditor()); + Display.getDefault().asyncExec(new Runnable() { + public void run() { + if (checkDisposed()) { + return; + } + + // refresh should only influence the master sub views + if (tableInputController.getSubViewClassification() == SubViewClassification.MASTER) { + Object input = tableInputController.getTableInput(); + tableViewer.setInput(input); + if (tableViewer.getTable().isVisible()) { + tableViewer.refresh(); + + // if we use check style, set elements to the initial state + if (tableInputController.isCheckStyle()) { + for (TableItem tableItem : tableViewer.getTable().getItems()) { + tableItem.setChecked(tableInputController.areItemsInitiallyChecked()); + } + } + } + + } + } + }); + } catch (Throwable throwable) { // NOPMD + throw new RuntimeException("Unknown exception occurred trying to refresh the view.", throwable); + } finally { + jobInSchedule = false; + } + + return Status.OK_STATUS; + } + }; + job.schedule(); + } + } + + /** + * {@inheritDoc} + */ + public void setDataInput(List data) { + if (checkDisposed()) { + return; + } + + if (tableInputController.canOpenInput(data)) { + tableViewer.setInput(data); + tableViewer.refresh(true); + } + } + + /** + * {@inheritDoc} + */ + public Control getControl() { + return tableViewer.getControl(); + } + + /** + * {@inheritDoc} + */ + public ISelectionProvider getSelectionProvider() { + return tableViewer; + } + + /** + * {@inheritDoc} + */ + public Set getPreferenceIds() { + return tableInputController.getPreferenceIds(); + } + + /** + * {@inheritDoc} + */ + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + if (checkDisposed()) { + return; + } + + if (PreferenceId.ITEMCOUNT.equals(preferenceEvent.getPreferenceId())) { + Map preferenceMap = preferenceEvent.getPreferenceMap(); + int limit = (Integer) preferenceMap.get(PreferenceId.ItemCount.COUNT_SELECTION_ID); + tableInputController.setLimit(limit); + this.doRefresh(); + } + + // we are suspending the table redraw while controller handles the event to disable any + // updates on the table by controller + tableViewer.getTable().setRedraw(false); + tableInputController.preferenceEventFired(preferenceEvent); + tableViewer.getTable().setRedraw(true); + + switch (preferenceEvent.getPreferenceId()) { + case CLEAR_BUFFER: + if (tableInputController.getPreferenceIds().contains(PreferenceId.CLEAR_BUFFER)) { + tableViewer.refresh(); + } + break; + case TIME_RESOLUTION: + if (tableInputController.getPreferenceIds().contains(PreferenceId.TIME_RESOLUTION)) { + tableViewer.refresh(); + } + break; + case INVOCATION_SUBVIEW_MODE: + if (tableInputController.getPreferenceIds().contains(PreferenceId.INVOCATION_SUBVIEW_MODE)) { + tableViewer.refresh(); + } + break; + default: + break; + } + } + + /** + * Returns the table input controller. + * + * @return The table input controller. + */ + public TableInputController getTableInputController() { + return tableInputController; + } + + /** + * Return the names of all columns in the table. Not visible columns names will also be + * included. The order of the names will be same to the initial table column order, thus not + * reflecting the current state of the table if the columns were moved. + * + * @return List of column names. + */ + public List getColumnNames() { + List names = new ArrayList(); + for (TableColumn column : tableViewer.getTable().getColumns()) { + names.add(column.getText()); + } + return names; + } + + /** + * + * @return The list of integers representing the column order in the table. Note that only + * columns that are currently visible will be included in the list. + * @see Table#getColumnOrder() + */ + public List getColumnOrder() { + int[] order = tableViewer.getTable().getColumnOrder(); + List orderWithoutHidden = new ArrayList(); + for (int index : order) { + if (tableViewer.getTable().getColumns()[index].getWidth() > 0) { + orderWithoutHidden.add(index); + } + } + return orderWithoutHidden; + } + + /** + * {@inheritDoc} + */ + @Override + public ISubView getSubViewWithInputController(Class inputControllerClass) { + if (Objects.equals(inputControllerClass, tableInputController.getClass())) { + return this; + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public SearchResult executeSearch(SearchCriteria searchCriteria) { + return tableViewerSearchHelper.executeSearch(searchCriteria); + } + + /** + * {@inheritDoc} + */ + @Override + public SearchResult next() { + return tableViewerSearchHelper.next(); + } + + /** + * {@inheritDoc} + */ + @Override + public SearchResult previous() { + return tableViewerSearchHelper.previous(); + } + + /** + * {@inheritDoc} + */ + @Override + public void clearSearch() { + tableViewerSearchHelper.clearSearch(); + } + + /** + * Returns true if the table in the sub-view is disposed. False otherwise. + * + * @return Returns true if the table in the sub-view is disposed. False otherwise. + */ + private boolean checkDisposed() { + return tableViewer.getTable().isDisposed(); + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + tableInputController.dispose(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/TableViewerComparator.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/TableViewerComparator.java new file mode 100644 index 000000000..0e5a458f3 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/TableViewerComparator.java @@ -0,0 +1,44 @@ +package info.novatec.inspectit.rcp.editor.table; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.rcp.editor.viewers.AbstractViewerComparator; + +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; + +/** + * Local table viewer comparator uses provided comparators to sort specific columns. + * + * @author Ivan Senic + * + * @param + * Type for which comparator is created. + */ +public class TableViewerComparator extends AbstractViewerComparator { + + /** + * Adds a column to this comparator so it can be used to sort by. + * + * @param column + * The {@link TableColumn} implementation. comparatorProvider The id of the + * {@link TableColumn} (user-defined). + * @param comparator + * Comparator that will be used for the given column. + */ + public final void addColumn(final TableColumn column, final ResultComparator comparator) { + column.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + toggleSortColumn(comparator); + + Table table = column.getParent(); + table.setSortColumn(column); + table.setSortDirection(getSortState().getSwtDirection()); + } + }); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/AbstractHttpInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/AbstractHttpInputController.java new file mode 100644 index 000000000..93f0799cd --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/AbstractHttpInputController.java @@ -0,0 +1,169 @@ +package info.novatec.inspectit.rcp.editor.table.input; + +import info.novatec.inspectit.cmr.service.IHttpTimerDataAccessService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId.LiveMode; +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; +import info.novatec.inspectit.rcp.preferences.PreferencesUtils; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.util.ArrayList; +import java.util.Date; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.StyledString; + +/** + * Reduce the redundancy in http controllers. Still as the de-facto standard of realizing + * controllers is based on the enum to create the columns lots of code needs to be doubled. We can + * address this in the future in a better way. + * + * @author Stefan Siegl + */ +public abstract class AbstractHttpInputController extends AbstractTableInputController { + + /** + * Http Timer data access service. + */ + protected IHttpTimerDataAccessService httptimerDataAccessService; + + /** + * List of Timer data to be displayed. + */ + protected List timerDataList = new ArrayList(); + + /** + * Template object used for querying. + */ + protected HttpTimerData template; + + /** + * Empty styled string. + */ + protected final StyledString emptyStyledString = new StyledString(); + + /** + * Date to display invocations from. + */ + protected Date fromDate = null; + + /** + * Date to display invocations to. + */ + protected Date toDate = null; + + /** + * Are we in live mode. + */ + protected boolean autoUpdate = LiveMode.ACTIVE_DEFAULT; + + /** + * Decimal places. + */ + protected int timeDecimalPlaces = PreferencesUtils.getIntValue(PreferencesConstants.DECIMAL_PLACES); + + /** + * Flag identifying whether the aggregation should take the request method into account. + */ + protected boolean httpCatorizationOnRequestMethodActive = false; + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + template = new HttpTimerData(); + template.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); + + httptimerDataAccessService = inputDefinition.getRepositoryDefinition().getHttpTimerDataAccessService(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canOpenInput(List data) { + if (null == data) { + return false; + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Object getTableInput() { + return timerDataList; + } + + @Override + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + if (null == preferenceEvent) { + return; + } + + switch (preferenceEvent.getPreferenceId()) { + case TIMELINE: + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeLine.FROM_DATE_ID)) { + fromDate = (Date) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeLine.FROM_DATE_ID); + } + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeLine.TO_DATE_ID)) { + toDate = (Date) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeLine.TO_DATE_ID); + } + break; + case LIVEMODE: + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.LiveMode.BUTTON_LIVE_ID)) { + autoUpdate = (Boolean) preferenceEvent.getPreferenceMap().get(PreferenceId.LiveMode.BUTTON_LIVE_ID); + } + break; + case TIME_RESOLUTION: + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeResolution.TIME_DECIMAL_PLACES_ID)) { + timeDecimalPlaces = (Integer) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeResolution.TIME_DECIMAL_PLACES_ID); + } + break; + case HTTP_AGGREGATION_REQUESTMETHOD: + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.HttpAggregationRequestMethod.BUTTON_HTTP_AGGREGATION_REQUESTMETHOD_ID)) { + httpCatorizationOnRequestMethodActive = (Boolean) preferenceEvent.getPreferenceMap().get(PreferenceId.HttpAggregationRequestMethod.BUTTON_HTTP_AGGREGATION_REQUESTMETHOD_ID); + } + break; + default: + break; + } + } + + /** + * {@inheritDoc} + */ + @Override + public Set getPreferenceIds() { + Set preferences = EnumSet.noneOf(PreferenceId.class); + if (getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) { + preferences.add(PreferenceId.CLEAR_BUFFER); + preferences.add(PreferenceId.LIVEMODE); + } + preferences.add(PreferenceId.UPDATE); + preferences.add(PreferenceId.TIMELINE); + preferences.add(PreferenceId.HTTP_AGGREGATION_REQUESTMETHOD); + preferences.add(PreferenceId.TIME_RESOLUTION); + return preferences; + } + + /** + * {@inheritDoc} + */ + @Override + public IContentProvider getContentProvider() { + return new ArrayContentProvider(); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/AbstractTableInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/AbstractTableInputController.java new file mode 100644 index 000000000..30fbf8423 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/AbstractTableInputController.java @@ -0,0 +1,216 @@ +package info.novatec.inspectit.rcp.editor.table.input; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.swt.widgets.TableColumn; + +/** + * The abstract class of the {@link TableInputController} interface to provide some standard + * methods. + * + * @author Patrice Bouillet + * + */ +public abstract class AbstractTableInputController implements TableInputController { + + /** + * Map of the enumeration keys and {@link TableViewerColumn}s. Subclasses can use utility + * methods to bound columns for later use. + */ + private Map, TableViewerColumn> tableViewerColumnMap = new HashMap, TableViewerColumn>(); + + /** + * The input definition. + */ + private InputDefinition inputDefinition; + + /** + * {@inheritDoc} + */ + public void setInputDefinition(InputDefinition inputDefinition) { + Assert.isNotNull(inputDefinition); + + this.inputDefinition = inputDefinition; + } + + /** + * Returns the input definition. + * + * @return The input definition. + */ + protected InputDefinition getInputDefinition() { + Assert.isNotNull(inputDefinition); + + return inputDefinition; + } + + /** + * {@inheritDoc} + *

+ * Return null by default, sub-classes may override. + */ + public Object getTableInput() { + return null; + } + + /** + * {@inheritDoc} + *

+ * Return an empty set by default, sub-classes may override. + */ + public Set getPreferenceIds() { + return Collections.emptySet(); + } + + /** + * {@inheritDoc} + *

+ * Do nothing by default, sub-classes may override. + */ + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + } + + /** + * {@inheritDoc} + *

+ * Do nothing by default, sub-classes may override. + */ + public void doRefresh(IProgressMonitor monitor, IRootEditor rootEditor) { + } + + /** + * {@inheritDoc} + *

+ * Do nothing by default, sub-classes may override. + */ + public void doubleClick(DoubleClickEvent event) { + } + + /** + * {@inheritDoc} + *

+ * Return null by default, sub-classes may override. + */ + public boolean canOpenInput(List data) { + return false; + } + + /** + * {@inheritDoc} + *

+ * Do nothing by default, sub-classes may override. + */ + public void setLimit(int limit) { + } + + /** + * {@inheritDoc} + */ + @Override + public Object[] getObjectsToSearch(Object tableInput) { + if (tableInput instanceof Object[]) { + return (Object[]) tableInput; + } + if (tableInput instanceof Collection) { + return ((Collection) tableInput).toArray(); + } + return new Object[0]; + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + + /** + * {@inheritDoc} + *

+ * By default controller sets the sub view to be master. + */ + @Override + public SubViewClassification getSubViewClassification() { + return SubViewClassification.MASTER; + } + + /** + * {@inheritDoc} + *

+ * Returns true, classes may override. + */ + @Override + public boolean canAlterColumnWidth(TableColumn tableColumn) { + return true; + } + + /** + * {@inheritDoc} + *

+ * By default does nothing, sub-classes may override. + */ + @Override + public void objectChecked(Object object, boolean checked) { + } + + /** + * {@inheritDoc} + *

+ * By default false. + */ + @Override + public boolean isCheckStyle() { + return false; + } + + /** + * {@inheritDoc} + *

+ * By default false. + */ + @Override + public boolean areItemsInitiallyChecked() { + return false; + } + + /** + * Maps a column with the enumeration key. The implementing classes should map each column they + * create to the enum that represents that column. Later on the column can be retrieved with the + * enum key if needed. + * + * @param key + * Enumeration that represents the column. + * @param column + * Created column to be mapped. + */ + public void mapTableViewerColumn(Enum key, TableViewerColumn column) { + tableViewerColumnMap.put(key, column); + } + + /** + * Returns the column that has been mapped with the given enum key. Enum should represent the + * wanted column. + * + * @param key + * Enumeration that represents the column. + * @return Returns the column that has been mapped with the given enum key or null + * if no mapping has been done. + */ + public TableViewerColumn getMappedTableViewerColumn(Enum key) { + return tableViewerColumnMap.get(key); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/AggregatedTimerSummaryInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/AggregatedTimerSummaryInputController.java new file mode 100644 index 000000000..45e5b40e2 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/AggregatedTimerSummaryInputController.java @@ -0,0 +1,301 @@ +package info.novatec.inspectit.rcp.editor.table.input; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.indexing.aggregation.impl.AggregationPerformer; +import info.novatec.inspectit.indexing.aggregation.impl.TimerDataAggregator; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; + +/** + * This input controller displays an aggregated summary of the timer data objects in a table. + * + * @author Patrice Bouillet + * + */ +public class AggregatedTimerSummaryInputController extends AbstractTableInputController { + + /** + * The private inner enumeration used to define the used IDs which are mapped into the columns. + * The order in this enumeration represents the order of the columns. If it is reordered, + * nothing else has to be changed. + * + * @author Patrice Bouillet + * + */ + private static enum Column { + /** The count column. */ + COUNT("Count", 100, null), + /** The average column. */ + AVERAGE("Avg (ms)", 100, null), + /** The minimum column. */ + MIN("Min (ms)", 100, null), + /** The maximum column. */ + MAX("Max (ms)", 100, null), + /** The duration column. */ + DURATION("Duration (ms)", 100, null), + /** The average exclusive duration column. */ + EXCLUSIVEAVERAGE("Exc. Avg (ms)", 100, null), + /** The min exclusive duration column. */ + EXCLUSIVEMIN("Exc. Min (ms)", 100, null), + /** The max exclusive duration column. */ + EXCLUSIVEMAX("Exc. Max (ms)", 100, null); + + /** The name. */ + private String name; + /** The width of the column. */ + private int width; + /** The image descriptor. Can be null */ + private Image image; + + /** + * Default constructor which creates a column enumeration object. + * + * @param name + * The name of the column. + * @param width + * The width of the column. + * @param imageName + * The name of the image. Names are defined in + * {@link info.novatec.inspectit.rcp.InspectITImages}. + */ + private Column(String name, int width, String imageName) { + this.name = name; + this.width = width; + this.image = InspectIT.getDefault().getImage(imageName); + } + + /** + * Converts an ordinal into a column. + * + * @param i + * The ordinal. + * @return The appropriate column. + */ + public static Column fromOrd(int i) { + if (i < 0 || i >= Column.values().length) { + throw new IndexOutOfBoundsException("Invalid ordinal"); + } + return Column.values()[i]; + } + } + + /** + * {@inheritDoc} + */ + public void createColumns(TableViewer tableViewer) { + for (Column column : Column.values()) { + TableViewerColumn viewerColumn = new TableViewerColumn(tableViewer, SWT.NONE); + viewerColumn.getColumn().setMoveable(true); + viewerColumn.getColumn().setResizable(true); + viewerColumn.getColumn().setText(column.name); + viewerColumn.getColumn().setWidth(column.width); + if (null != column.image) { + viewerColumn.getColumn().setImage(column.image); + } + } + } + + /** + * {@inheritDoc} + */ + public ViewerComparator getComparator() { + // no sorting + return null; + } + + /** + * {@inheritDoc} + */ + public IContentProvider getContentProvider() { + return new ContentProvider(); + } + + /** + * The content provider. + * + * @author Patrice Bouillet + * + */ + private static final class ContentProvider implements IStructuredContentProvider { + + /** + * Aggregation performer. + */ + private final AggregationPerformer aggregationPerformer = new AggregationPerformer<>(new TimerDataAggregator()); + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public Object[] getElements(Object inputElement) { + List timerData = (List) inputElement; + aggregationPerformer.reset(); + aggregationPerformer.processCollection(timerData); + return aggregationPerformer.getResultList().toArray(); + } + + /** + * {@inheritDoc} + */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + + } + + /** + * {@inheritDoc} + */ + public IBaseLabelProvider getLabelProvider() { + return new LabelProvider(); + } + + /** + * The label provider. + * + * @author Patrice Bouillet + * + */ + private final class LabelProvider extends StyledCellIndexLabelProvider { + + /** + * Creates the styled text. + * + * @param element + * The element to create the styled text for. + * @param index + * The index in the column. + * @return The created styled string. + */ + @Override + public StyledString getStyledText(Object element, int index) { + TimerData data = (TimerData) element; + Column enumId = Column.fromOrd(index); + + return getStyledTextForColumn(data, enumId); + } + + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canOpenInput(List data) { + if (null == data || data.isEmpty()) { + return true; + } + + if (!(data.get(0) instanceof TimerData)) { + return false; + } + + return true; + } + + /** + * Returns the styled text for a specific column. + * + * @param data + * The data object to extract the information from. + * @param enumId + * The enumeration ID. + * @return The styled string containing the information from the data object. + */ + private StyledString getStyledTextForColumn(TimerData data, Column enumId) { + switch (enumId) { + case COUNT: + return new StyledString(Long.toString(data.getCount())); + case AVERAGE: + return new StyledString(NumberFormatter.formatDouble(data.getAverage())); + case MIN: + return new StyledString(NumberFormatter.formatDouble(data.getMin())); + case MAX: + return new StyledString(NumberFormatter.formatDouble(data.getMax())); + case DURATION: + return new StyledString(NumberFormatter.formatDouble(data.getDuration())); + case EXCLUSIVEAVERAGE: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveAverage())); + } else { + return new StyledString(""); + } + case EXCLUSIVEMAX: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveMax())); + } else { + return new StyledString(""); + } + case EXCLUSIVEMIN: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveMin())); + } else { + return new StyledString(""); + } + default: + return new StyledString("error"); + } + } + + /** + * {@inheritDoc} + */ + public String getReadableString(Object object) { + if (object instanceof TimerData) { + TimerData data = (TimerData) object; + StringBuilder sb = new StringBuilder(); + for (Column column : Column.values()) { + sb.append(getStyledTextForColumn(data, column).toString()); + sb.append('\t'); + } + return sb.toString(); + } + throw new RuntimeException("Could not create the human readable string!"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getColumnValues(Object object) { + if (object instanceof TimerData) { + TimerData data = (TimerData) object; + List values = new ArrayList(); + for (Column column : Column.values()) { + values.add(getStyledTextForColumn(data, column).toString()); + } + return values; + } + throw new RuntimeException("Could not create the column values!"); + } + + /** + * {@inheritDoc} + */ + @Override + public SubViewClassification getSubViewClassification() { + return SubViewClassification.SLAVE; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/ExceptionSensorInvocInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/ExceptionSensorInvocInputController.java new file mode 100644 index 000000000..cd34d8f83 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/ExceptionSensorInvocInputController.java @@ -0,0 +1,584 @@ +package info.novatec.inspectit.rcp.editor.table.input; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.service.ICachedDataService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.ExceptionEvent; +import info.novatec.inspectit.communication.comparator.AggregatedExceptionSensorDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.DefaultDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.ExceptionSensorDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.IDataComparator; +import info.novatec.inspectit.communication.comparator.MethodSensorDataComparatorEnum; +import info.novatec.inspectit.communication.data.AggregatedExceptionSensorData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.indexing.aggregation.impl.AggregationPerformer; +import info.novatec.inspectit.indexing.aggregation.impl.ExceptionDataAggregator; +import info.novatec.inspectit.indexing.aggregation.impl.ExceptionDataAggregator.ExceptionAggregationType; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.IPreferenceGroup; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.table.TableViewerComparator; +import info.novatec.inspectit.rcp.editor.viewers.RawAggregatedResultComparator; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.handlers.ShowHideColumnsHandler; +import info.novatec.inspectit.rcp.model.ExceptionImageFactory; +import info.novatec.inspectit.rcp.model.ModifiersImageFactory; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.TableColumn; + +/** + * This input controller displays the contents of {@link ExceptionSensorData} objects in an + * invocation sequence. + * + * @author Eduard Tudenhoefner + * + */ +public class ExceptionSensorInvocInputController extends AbstractTableInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.table.exceptionsensorinvoc"; + + /** + * The resource manager is used for the images etc. + */ + private LocalResourceManager resourceManager = new LocalResourceManager(JFaceResources.getResources()); + + /** + * The private inner enumeration used to define the used IDs which are mapped into the columns. + * The order in this enumeration represents the order of the columns. If it is reordered, + * nothing else has to be changed. + * + * @author Eduard Tudenhoefner + * + */ + private static enum Column { + /** The timestamp column. */ + TIMESTAMP("Timestamp", 150, InspectITImages.IMG_TIMESTAMP, false, true, DefaultDataComparatorEnum.TIMESTAMP), + /** The fqn column. */ + FQN("Fully-Qualified Name", 400, InspectITImages.IMG_CLASS, true, true, ExceptionSensorDataComparatorEnum.FQN), + /** The count column. */ + CREATED("Created", 60, null, true, false, AggregatedExceptionSensorDataComparatorEnum.CREATED), + /** The RETHROWN column. */ + RETHROWN("Rethrown", 60, null, true, false, AggregatedExceptionSensorDataComparatorEnum.RETHROWN), + /** The HANDLED column. */ + HANDLED("Handled", 60, null, true, false, AggregatedExceptionSensorDataComparatorEnum.HANDLED), + /** The constructor column. */ + CONSTRUCTOR("Constructor", 250, InspectITImages.IMG_METHOD_PUBLIC, false, true, MethodSensorDataComparatorEnum.METHOD), + /** The error message column. */ + ERROR_MESSAGE("Error Message", 250, null, false, true, ExceptionSensorDataComparatorEnum.ERROR_MESSAGE); + + /** The name. */ + private String name; + /** The width of the column. */ + private int width; + /** The image descriptor. Can be null */ + private Image image; + /** If the column should be shown in aggregated mode. */ + private boolean showInAggregatedMode; + /** If the column should be shown in raw mode. */ + private boolean showInRawMode; + /** Comparator for the column. */ + private IDataComparator dataComparator; + + /** + * Default constructor which creates a column enumeration object. + * + * @param name + * The name of the column. + * @param width + * The width of the column. + * @param imageName + * The name of the image. Names are defined in {@link InspectITImages}. + * @param showInAggregatedMode + * If the column should be shown in aggregated mode. + * @param showInRawMode + * If the column should be shown in raw mode. + * @param dataComparator + * Comparator for the column. + * + */ + private Column(String name, int width, String imageName, boolean showInAggregatedMode, boolean showInRawMode, IDataComparator dataComparator) { + this.name = name; + this.width = width; + this.image = InspectIT.getDefault().getImage(imageName); + this.showInAggregatedMode = showInAggregatedMode; + this.showInRawMode = showInRawMode; + this.dataComparator = dataComparator; + } + + /** + * Converts an ordinal into a column. + * + * @param i + * The ordinal. + * @return The appropriate column. + */ + public static Column fromOrd(int i) { + if (i < 0 || i >= Column.values().length) { + throw new IndexOutOfBoundsException("Invalid ordinal"); + } + return Column.values()[i]; + } + + } + + /** + * This data access service is needed because of the ID mappings. + */ + private ICachedDataService cachedDataService; + + /** + * List that is displayed after processing the invocation. + */ + private List exceptionSensorDataList; + + /** + * Empty styled string. + */ + private final StyledString emptyStyledString = new StyledString(); + + /** + * Should view display raw mode or not. + */ + private boolean rawMode = false; + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + cachedDataService = inputDefinition.getRepositoryDefinition().getCachedDataService(); + } + + /** + * {@inheritDoc} + */ + public void createColumns(TableViewer tableViewer) { + for (Column column : Column.values()) { + TableViewerColumn viewerColumn = new TableViewerColumn(tableViewer, SWT.NONE); + viewerColumn.getColumn().setMoveable(true); + viewerColumn.getColumn().setResizable(true); + viewerColumn.getColumn().setText(column.name); + if (column.showInAggregatedMode) { + viewerColumn.getColumn().setWidth(column.width); + } else { + viewerColumn.getColumn().setWidth(0); + } + if (null != column.image) { + viewerColumn.getColumn().setImage(column.image); + } + super.mapTableViewerColumn(column, viewerColumn); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canAlterColumnWidth(TableColumn tableColumn) { + for (Column column : Column.values()) { + if (Objects.equals(getMappedTableViewerColumn(column).getColumn(), tableColumn)) { + return (column.showInRawMode && rawMode) || (column.showInAggregatedMode && !rawMode); + } + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getPreferenceIds() { + Set preferences = EnumSet.noneOf(PreferenceId.class); + preferences.add(PreferenceId.INVOCATION_SUBVIEW_MODE); + return preferences; + } + + /** + * {@inheritDoc} + */ + @Override + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + if (PreferenceId.INVOCATION_SUBVIEW_MODE.equals(preferenceEvent.getPreferenceId())) { + Map preferenceMap = preferenceEvent.getPreferenceMap(); + if (null != preferenceMap && preferenceMap.containsKey(PreferenceId.InvocationSubviewMode.RAW)) { + Boolean isRawMode = (Boolean) preferenceMap.get(PreferenceId.InvocationSubviewMode.RAW); + + // first show/hide columns and then change the rawMode value + handleRawAggregatedColumnVisibility(isRawMode.booleanValue()); + rawMode = isRawMode.booleanValue(); + } + } + } + + /** + * Handles the raw and aggregated columns hiding/showing. + * + * @param rawMode + * Is raw mode active. + */ + private void handleRawAggregatedColumnVisibility(boolean rawMode) { + for (Column column : Column.values()) { + if (rawMode) { + if (column.showInRawMode && !column.showInAggregatedMode && !ShowHideColumnsHandler.isColumnHidden(this.getClass(), column.name)) { + Integer width = ShowHideColumnsHandler.getRememberedColumnWidth(this.getClass(), column.name); + getMappedTableViewerColumn(column).getColumn().setWidth((null != width) ? width.intValue() : column.width); + } else if (!column.showInRawMode && column.showInAggregatedMode) { + getMappedTableViewerColumn(column).getColumn().setWidth(0); + } + } else { + if (!column.showInRawMode && column.showInAggregatedMode && !ShowHideColumnsHandler.isColumnHidden(this.getClass(), column.name)) { + Integer width = ShowHideColumnsHandler.getRememberedColumnWidth(this.getClass(), column.name); + getMappedTableViewerColumn(column).getColumn().setWidth((null != width) ? width.intValue() : column.width); + } else if (column.showInRawMode && !column.showInAggregatedMode) { + getMappedTableViewerColumn(column).getColumn().setWidth(0); + } + } + } + } + + /** + * {@inheritDoc} + */ + public IContentProvider getContentProvider() { + return new ExceptionSensorInvocContentProvider(); + } + + /** + * {@inheritDoc} + */ + public ViewerComparator getComparator() { + TableViewerComparator exceptionSensorInputViewerComparator = new TableViewerComparator(); + for (Column column : Column.values()) { + RawAggregatedResultComparator comparator = new RawAggregatedResultComparator(column.dataComparator, cachedDataService, + column.showInRawMode, column.showInAggregatedMode) { + @Override + protected boolean isRawMode() { + return rawMode; + } + }; + exceptionSensorInputViewerComparator.addColumn(getMappedTableViewerColumn(column).getColumn(), comparator); + } + + return exceptionSensorInputViewerComparator; + } + + /** + * {@inheritDoc} + */ + public IBaseLabelProvider getLabelProvider() { + return new ExceptionSensorInvocLabelProvider(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canOpenInput(List data) { + if (null == data) { + return false; + } + + if (data.isEmpty()) { + return true; + } + + if (!(data.get(0) instanceof InvocationSequenceData)) { + return false; + } + + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void setLimit(int limit) { + } + + /** + * {@inheritDoc} + */ + public void update(IProgressMonitor monitor) { + } + + /** + * The content provider for this view. + * + * @author Eduard Tudenhoefner + * + */ + private final class ExceptionSensorInvocContentProvider implements IStructuredContentProvider { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public Object[] getElements(Object inputElement) { + List invocationSequenceDataList = (List) inputElement; + exceptionSensorDataList = getRawExceptionSensorDataList(invocationSequenceDataList, new ArrayList()); + if (!rawMode) { + AggregationPerformer aggregationPerformer = new AggregationPerformer(new ExceptionDataAggregator(ExceptionAggregationType.THROWABLE_TYPE)); + aggregationPerformer.processCollection(exceptionSensorDataList); + exceptionSensorDataList = aggregationPerformer.getResultList(); + } + return exceptionSensorDataList.toArray(); + } + + /** + * Returns raw list of exceptions. + * + * @param invocationSequenceDataList + * Invocations. + * @param exceptionSensorDataList + * Result list. + * @return List of exceptions for raw display. + */ + private List getRawExceptionSensorDataList(List invocationSequenceDataList, List exceptionSensorDataList) { + for (InvocationSequenceData invocationSequenceData : invocationSequenceDataList) { + if (null != invocationSequenceData.getExceptionSensorDataObjects() && !invocationSequenceData.getExceptionSensorDataObjects().isEmpty()) { + for (ExceptionSensorData object : invocationSequenceData.getExceptionSensorDataObjects()) { + if (ObjectUtils.equals(object.getExceptionEvent(), ExceptionEvent.CREATED)) { + exceptionSensorDataList.add((ExceptionSensorData) object); + } + } + } + if (null != invocationSequenceData.getNestedSequences() && !invocationSequenceData.getNestedSequences().isEmpty()) { + getRawExceptionSensorDataList(invocationSequenceData.getNestedSequences(), exceptionSensorDataList); + } + } + + return exceptionSensorDataList; + } + + /** + * {@inheritDoc} + */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + + } + + /** + * The exception sensor label provider used by this view. + * + * @author Eduard Tudenhoefner + * + */ + private final class ExceptionSensorInvocLabelProvider extends StyledCellIndexLabelProvider { + + /** + * Creates the styled text. + * + * @param element + * The element to create the styled text for. + * @param index + * The index in the column. + * @return The created styled string. + */ + @Override + public StyledString getStyledText(Object element, int index) { + ExceptionSensorData data = (ExceptionSensorData) element; + Column enumId = Column.fromOrd(index); + MethodIdent methodIdent = null; + if (0 != data.getMethodIdent()) { + methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + } + + return getStyledTextForColumn(data, methodIdent, enumId); + } + + /** + * Returns the column image for the given element at the given index. + * + * @param element + * The element. + * @param index + * The index. + * @return Returns the Image. + */ + @Override + public Image getColumnImage(Object element, int index) { + if (rawMode) { + Column enumId = Column.fromOrd(index); + switch (enumId) { + case CONSTRUCTOR: + ExceptionSensorData data = (ExceptionSensorData) element; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + Image image = ModifiersImageFactory.getImage(methodIdent.getModifiers()); + image = ExceptionImageFactory.decorateImageWithException(image, data, resourceManager); + return image; + default: + return null; + } + } else { + return null; + } + } + } + + /** + * Returns the styled text for a specific column. + * + * @param data + * The data object to extract the information from. + * @param methodIdent + * The method ident object where to retrieve information from. + * @param enumId + * The enumeration ID. + * @return The styled string containing the information from the data object. + */ + private StyledString getStyledTextForColumn(ExceptionSensorData data, MethodIdent methodIdent, Column enumId) { + if (null != data) { + switch (enumId) { + case TIMESTAMP: + if (rawMode) { + return new StyledString(NumberFormatter.formatTimeWithMillis(data.getTimeStamp())); + } else { + return emptyStyledString; + } + case FQN: + return new StyledString(data.getThrowableType()); + case CREATED: + if (!rawMode && data instanceof AggregatedExceptionSensorData) { + return new StyledString(NumberFormatter.formatLong(((AggregatedExceptionSensorData) data).getCreated())); + } else if (ExceptionEvent.CREATED.equals(data.getExceptionEvent())) { + return new StyledString("Yes"); + } else { + return emptyStyledString; + } + case RETHROWN: + if (!rawMode && data instanceof AggregatedExceptionSensorData) { + return new StyledString(NumberFormatter.formatLong(((AggregatedExceptionSensorData) data).getPassed())); + } else if (ExceptionEvent.PASSED.equals(data.getExceptionEvent())) { + return new StyledString("Yes"); + } else { + return emptyStyledString; + } + case HANDLED: + if (!rawMode && data instanceof AggregatedExceptionSensorData) { + return new StyledString(NumberFormatter.formatLong(((AggregatedExceptionSensorData) data).getHandled())); + } else if (ExceptionEvent.HANDLED.equals(data.getExceptionEvent())) { + return new StyledString("Yes"); + } else { + return emptyStyledString; + } + case CONSTRUCTOR: + if (rawMode) { + return new StyledString(TextFormatter.getMethodWithParameters(methodIdent)); + } else { + return emptyStyledString; + } + case ERROR_MESSAGE: + if (rawMode) { + StyledString styledString = new StyledString(); + if (null != data.getErrorMessage()) { + styledString.append(data.getErrorMessage()); + } + return styledString; + } else { + return emptyStyledString; + } + default: + return new StyledString("error"); + } + } + return new StyledString("error"); + } + + /** + * {@inheritDoc} + */ + public String getReadableString(Object object) { + if (object instanceof ExceptionSensorData) { + ExceptionSensorData data = (ExceptionSensorData) object; + StringBuilder sb = new StringBuilder(); + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + for (Column column : Column.values()) { + sb.append(getStyledTextForColumn(data, methodIdent, column).toString()); + sb.append('\t'); + } + return sb.toString(); + } + throw new RuntimeException("Could not create the human readable string!"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getColumnValues(Object object) { + if (object instanceof ExceptionSensorData) { + ExceptionSensorData data = (ExceptionSensorData) object; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + List values = new ArrayList(); + for (Column column : Column.values()) { + values.add(getStyledTextForColumn(data, methodIdent, column).toString()); + } + return values; + } + throw new RuntimeException("Could not create the column values!"); + } + + /** + * {@inheritDoc} + */ + @Override + public Object[] getObjectsToSearch(Object tableInput) { + return exceptionSensorDataList.toArray(); + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + resourceManager.dispose(); + } + + /** + * {@inheritDoc} + */ + @Override + public SubViewClassification getSubViewClassification() { + return SubViewClassification.SLAVE; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/GroupedExceptionOverviewInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/GroupedExceptionOverviewInputController.java new file mode 100644 index 000000000..130e8d21b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/GroupedExceptionOverviewInputController.java @@ -0,0 +1,510 @@ +package info.novatec.inspectit.rcp.editor.table.input; + +import info.novatec.inspectit.cmr.service.ICachedDataService; +import info.novatec.inspectit.cmr.service.IExceptionDataAccessService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.comparator.AggregatedExceptionSensorDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.ExceptionSensorDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.IDataComparator; +import info.novatec.inspectit.communication.comparator.InvocationAwareDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.communication.data.AggregatedExceptionSensorData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory; +import info.novatec.inspectit.rcp.editor.preferences.IPreferenceGroup; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; +import info.novatec.inspectit.rcp.editor.table.TableViewerComparator; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; + +/** + * + * @author Eduard Tudenhoefner + * + */ +public class GroupedExceptionOverviewInputController extends AbstractTableInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.table.groupedexceptionoverview"; + + /** + * The private inner enumeration used to define the used IDs which are mapped into the columns. + * The order in this enumeration represents the order of the columns. If it is reordered, + * nothing else has to be changed. + * + * @author Eduard Tudenhoefner + * + */ + private static enum Column { + /** The class column. */ + FQN("Fully-Qualified Name", 450, InspectITImages.IMG_CLASS, ExceptionSensorDataComparatorEnum.FQN), + /** Invocation Affiliation. */ + INVOCATION_AFFILLIATION("In Invocations", 120, InspectITImages.IMG_INVOCATION, InvocationAwareDataComparatorEnum.INVOCATION_AFFILIATION), + /** The CREATED column. */ + CREATED("Created", 70, null, AggregatedExceptionSensorDataComparatorEnum.CREATED), + /** The RETHROWN column. */ + RETHROWN("Rethrown", 70, null, AggregatedExceptionSensorDataComparatorEnum.RETHROWN), + /** The HANDLED column. */ + HANDLED("Handled", 70, null, AggregatedExceptionSensorDataComparatorEnum.HANDLED); + + /** The name. */ + private String name; + /** The width of the column. */ + private int width; + /** The image descriptor. Can be null */ + private Image image; + /** Comparator for the column. */ + private IDataComparator dataComparator; + + /** + * Default constructor which creates a column enumeration object. + * + * @param name + * The name of the column. + * @param width + * The width of the column. + * @param imageName + * The name of the image. Names are defined in {@link InspectITImages}. + * @param dataComparator + * Comparator for the column. + */ + private Column(String name, int width, String imageName, IDataComparator dataComparator) { + this.name = name; + this.width = width; + this.image = InspectIT.getDefault().getImage(imageName); + this.dataComparator = dataComparator; + } + + /** + * Converts an ordinal into a column. + * + * @param i + * The ordinal. + * @return The appropriate column. + */ + public static Column fromOrd(int i) { + if (i < 0 || i >= Column.values().length) { + throw new IndexOutOfBoundsException("Invalid ordinal"); + } + return Column.values()[i]; + } + + } + + /** + * The template object which is send to the server. + */ + private ExceptionSensorData template; + + /** + * Indicates from which point in time data should be shown. + */ + private Date fromDate; + + /** + * Indicates until which point in time data should be shown. + */ + private Date toDate; + + /** + * The list of {@link ExceptionSensorData} objects which is displayed. + */ + private List exceptionSensorDataList = new ArrayList(); + + /** + * This map holds all objects that are needed to be represented in this view. It uses the fqn of + * an exception as the key. It contains as value the objects that are belonging to a specific + * exception class. + */ + private Map> overviewMap; + + /** + * The data access service to access the data on the CMR. + */ + private IExceptionDataAccessService dataAccessService; + + /** + * {@inheritDoc} + */ + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + template = new ExceptionSensorData(); + template.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); + template.setSensorTypeIdent(inputDefinition.getIdDefinition().getSensorTypeId()); + template.setMethodIdent(inputDefinition.getIdDefinition().getMethodId()); + + if (inputDefinition.hasInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.EXCEPTION_TYPE_EXTRAS_MARKER)) { + String throwableType = inputDefinition.getInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.EXCEPTION_TYPE_EXTRAS_MARKER).getThrowableType(); + template.setThrowableType(throwableType); + } + + dataAccessService = inputDefinition.getRepositoryDefinition().getExceptionDataAccessService(); + } + + /** + * {@inheritDoc} + */ + public void createColumns(TableViewer tableViewer) { + for (Column column : Column.values()) { + TableViewerColumn viewerColumn = new TableViewerColumn(tableViewer, SWT.NONE); + viewerColumn.getColumn().setMoveable(true); + viewerColumn.getColumn().setResizable(true); + viewerColumn.getColumn().setText(column.name); + viewerColumn.getColumn().setWidth(column.width); + if (null != column.image) { + viewerColumn.getColumn().setImage(column.image); + } + mapTableViewerColumn(column, viewerColumn); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Object getTableInput() { + // this list will be filled with data + return exceptionSensorDataList; + } + + /** + * {@inheritDoc} + */ + public IContentProvider getContentProvider() { + return new GroupedExceptionOverviewContentProvider(); + } + + /** + * {@inheritDoc} + */ + public IBaseLabelProvider getLabelProvider() { + return new GroupedExceptionOverviewLabelProvider(); + } + + /** + * {@inheritDoc} + */ + public ViewerComparator getComparator() { + ICachedDataService cachedDataService = getInputDefinition().getRepositoryDefinition().getCachedDataService(); + TableViewerComparator exceptionOverviewViewerComparator = new TableViewerComparator(); + for (Column column : Column.values()) { + ResultComparator resultComparator = new ResultComparator(column.dataComparator, cachedDataService); + exceptionOverviewViewerComparator.addColumn(getMappedTableViewerColumn(column).getColumn(), resultComparator); + } + + return exceptionOverviewViewerComparator; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getPreferenceIds() { + Set preferences = EnumSet.noneOf(PreferenceId.class); + if (getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) { + preferences.add(PreferenceId.CLEAR_BUFFER); + preferences.add(PreferenceId.LIVEMODE); + } + preferences.add(PreferenceId.UPDATE); + preferences.add(PreferenceId.TIMELINE); + return preferences; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canOpenInput(List data) { + if (data.isEmpty()) { + return true; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + switch (preferenceEvent.getPreferenceId()) { + case TIMELINE: + Map preferenceMap = preferenceEvent.getPreferenceMap(); + if (preferenceMap.containsKey(PreferenceId.TimeLine.FROM_DATE_ID)) { + fromDate = (Date) preferenceMap.get(PreferenceId.TimeLine.FROM_DATE_ID); + } + if (preferenceMap.containsKey(PreferenceId.TimeLine.TO_DATE_ID)) { + toDate = (Date) preferenceMap.get(PreferenceId.TimeLine.TO_DATE_ID); + } + break; + default: + break; + } + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public void doRefresh(IProgressMonitor monitor, IRootEditor rootEditor) { + monitor.beginTask("Updating Grouped Exception Overview", IProgressMonitor.UNKNOWN); + monitor.subTask("Retrieving the Grouped Exception Overview"); + List ungroupedList = null; + + // if fromDate and toDate are set, then we retrieve only the data for + // this time interval + if (null != fromDate && null != toDate) { + ungroupedList = dataAccessService.getDataForGroupedExceptionOverview(template, fromDate, toDate); + } else { + ungroupedList = dataAccessService.getDataForGroupedExceptionOverview(template); + } + + List groupedOverviewList = new ArrayList(); + overviewMap = new HashMap>(); + + for (AggregatedExceptionSensorData ungroupedObject : ungroupedList) { + List groupedObjects = Collections.EMPTY_LIST; + if (!overviewMap.containsKey(ungroupedObject.getThrowableType())) { + // map doesn't contain the actual exception class, so we create + // and add a new list for exception classes of the same type + groupedObjects = new ArrayList(); + groupedObjects.add(ungroupedObject); + overviewMap.put(ungroupedObject.getThrowableType(), groupedObjects); + } else { + // map contains the actual exception class, so we get the list + // and search for the object within the list where the counter + // values must be updated + groupedObjects = overviewMap.get(ungroupedObject.getThrowableType()); + groupedObjects.add(ungroupedObject); + } + } + + // we are creating the list that contains all object to be shown in the + // overview + for (Map.Entry> entry : overviewMap.entrySet()) { + String throwableType = entry.getKey(); + AggregatedExceptionSensorData data = createObjectForOverview(throwableType, entry.getValue()); + groupedOverviewList.add(data); + } + + if (null != groupedOverviewList) { + exceptionSensorDataList.clear(); + monitor.subTask("Displaying the Exception Overview"); + exceptionSensorDataList.addAll(groupedOverviewList); + } + + monitor.done(); + } + + /** + * Creates the {@link AggregatedExceptionSensorData} object for the table input from the list of + * same type aggregated objects. + * + * @param throwableType + * Throwable type. + * @param dataList + * List of {@link AggregatedExceptionSensorData} + * @return Aggregated data for the table. + */ + private AggregatedExceptionSensorData createObjectForOverview(String throwableType, List dataList) { + AggregatedExceptionSensorData data = new AggregatedExceptionSensorData(); + data.setThrowableType(throwableType); + + for (AggregatedExceptionSensorData object : dataList) { + if (data.getPlatformIdent() == 0) { + data.setPlatformIdent(object.getPlatformIdent()); + } + data.aggregateExceptionData(object); + } + + return data; + } + + /** + * {@inheritDoc} + */ + @Override + public void doubleClick(DoubleClickEvent event) { + final StructuredSelection selection = (StructuredSelection) event.getSelection(); + if (!selection.isEmpty()) { + try { + PlatformUI.getWorkbench().getProgressService().busyCursorWhile(new IRunnableWithProgress() { + public void run(final IProgressMonitor monitor) { + monitor.beginTask("Retrieving Exception Messages", IProgressMonitor.UNKNOWN); + AggregatedExceptionSensorData data = (AggregatedExceptionSensorData) selection.getFirstElement(); + final List dataList = (List) overviewMap.get(data.getThrowableType()); + + Display.getDefault().asyncExec(new Runnable() { + public void run() { + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + IWorkbenchPage page = window.getActivePage(); + IRootEditor rootEditor = (IRootEditor) page.getActiveEditor(); + rootEditor.setDataInput(dataList); + } + }); + monitor.done(); + } + }); + } catch (InvocationTargetException e) { + MessageDialog.openError(Display.getDefault().getActiveShell().getShell(), "Error", e.getCause().toString()); + } catch (InterruptedException e) { + MessageDialog.openInformation(Display.getDefault().getActiveShell().getShell(), "Cancelled", e.getCause().toString()); + } + } + } + + /** + * The label provider for this view. + * + * @author Eduard Tudenhoefner + * + */ + private final class GroupedExceptionOverviewLabelProvider extends StyledCellIndexLabelProvider { + + /** + * {@inheritDoc} + */ + @Override + protected StyledString getStyledText(Object element, int index) { + AggregatedExceptionSensorData data = (AggregatedExceptionSensorData) element; + Column enumId = Column.fromOrd(index); + + return getStyledTextForColumn(data, enumId); + } + + } + + /** + * The content provider for this view. + * + * @author Eduard Tudenhoefner + * + */ + private static final class GroupedExceptionOverviewContentProvider implements IStructuredContentProvider { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public Object[] getElements(Object inputElement) { + List exceptionSensorData = (List) inputElement; + return exceptionSensorData.toArray(); + } + + /** + * {@inheritDoc} + */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + + } + + /** + * Returns the styled text for a specific column. + * + * @param data + * The data object to extract the information from. + * @param enumId + * The enumeration ID. + * @return The styled string containing the information from the data object. + */ + private StyledString getStyledTextForColumn(AggregatedExceptionSensorData data, Column enumId) { + switch (enumId) { + case FQN: + return new StyledString(data.getThrowableType()); + case INVOCATION_AFFILLIATION: + int percentage = (int) (data.getInvocationAffiliationPercentage() * 100); + int invocations = 0; + if (null != data.getInvocationParentsIdSet()) { + invocations = data.getInvocationParentsIdSet().size(); + } + return TextFormatter.getInvocationAffilliationPercentageString(percentage, invocations); + case CREATED: + return new StyledString(String.valueOf(data.getCreated())); + case RETHROWN: + return new StyledString(String.valueOf(data.getPassed())); + case HANDLED: + return new StyledString(String.valueOf(data.getHandled())); + default: + return new StyledString("error"); + } + } + + /** + * {@inheritDoc} + */ + public String getReadableString(Object object) { + if (object instanceof AggregatedExceptionSensorData) { + AggregatedExceptionSensorData data = (AggregatedExceptionSensorData) object; + StringBuilder sb = new StringBuilder(); + for (Column column : Column.values()) { + sb.append(getStyledTextForColumn(data, column).toString()); + sb.append('\t'); + } + return sb.toString(); + } + throw new RuntimeException("Could not create the human readable string!"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getColumnValues(Object object) { + if (object instanceof AggregatedExceptionSensorData) { + AggregatedExceptionSensorData data = (AggregatedExceptionSensorData) object; + List values = new ArrayList(); + for (Column column : Column.values()) { + values.add(getStyledTextForColumn(data, column).toString()); + } + return values; + } + throw new RuntimeException("Could not create the column values!"); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/HttpTimerDataInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/HttpTimerDataInputController.java new file mode 100644 index 000000000..94360bd0e --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/HttpTimerDataInputController.java @@ -0,0 +1,609 @@ +package info.novatec.inspectit.rcp.editor.table.input; + +import info.novatec.inspectit.cmr.model.MethodSensorTypeIdent; +import info.novatec.inspectit.cmr.model.MethodSensorTypeIdentHelper; +import info.novatec.inspectit.cmr.service.ICachedDataService; +import info.novatec.inspectit.communication.IAggregatedData; +import info.novatec.inspectit.communication.comparator.HttpTimerDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.IDataComparator; +import info.novatec.inspectit.communication.comparator.InvocationAwareDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.communication.comparator.TimerDataComparatorEnum; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.indexing.aggregation.impl.AggregationPerformer; +import info.novatec.inspectit.indexing.aggregation.impl.HttpTimerDataAggregator; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; +import info.novatec.inspectit.rcp.editor.table.TableViewerComparator; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.util.data.RegExAggregatedHttpTimerData; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; + +/** + * InputController for HttpTimerData view. + * + * @author Stefan Siegl + */ +public class HttpTimerDataInputController extends AbstractHttpInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.table.aggregatedhttptimerdata"; + + /** + * The private inner enumeration used to define the used IDs which are mapped into the columns. + * The order in this enumeration represents the order of the columns. If it is reordered, + * nothing else has to be changed. + * + * @author Stefan Siegl + * + */ + private static enum Column { + /** The time column. */ + CHARTING("Charting", 20, null, TimerDataComparatorEnum.CHARTING), + /** The package column. */ + URI("URI", 300, InspectITImages.IMG_HTTP_URL, HttpTimerDataComparatorEnum.URI), + /** The http method. */ + HTTP_METHOD("Method", 80, null, HttpTimerDataComparatorEnum.HTTP_METHOD), + /** Invocation Affiliation. */ + INVOCATION_AFFILLIATION("In Invocations", 130, InspectITImages.IMG_INVOCATION, InvocationAwareDataComparatorEnum.INVOCATION_AFFILIATION), + /** The count column. */ + COUNT("Count", 60, null, TimerDataComparatorEnum.COUNT), + /** The average column. */ + AVERAGE("Avg (ms)", 60, null, TimerDataComparatorEnum.AVERAGE), + /** The minimum column. */ + MIN("Min (ms)", 60, null, TimerDataComparatorEnum.MIN), + /** The maximum column. */ + MAX("Max (ms)", 60, null, TimerDataComparatorEnum.MAX), + /** The duration column. */ + DURATION("Duration (ms)", 70, null, TimerDataComparatorEnum.DURATION), + /** The average exclusive duration column. */ + EXCLUSIVEAVERAGE("Exc. Avg (ms)", 80, null, TimerDataComparatorEnum.EXCLUSIVEAVERAGE), + /** The min exclusive duration column. */ + EXCLUSIVEMIN("Exc. Min (ms)", 80, null, TimerDataComparatorEnum.EXCLUSIVEMIN), + /** The max exclusive duration column. */ + EXCLUSIVEMAX("Exc. Max (ms)", 80, null, TimerDataComparatorEnum.EXCLUSIVEMAX), + /** The total exclusive duration column. */ + EXCLUSIVESUM("Exc. duration (ms)", 80, null, TimerDataComparatorEnum.EXCLUSIVEDURATION), + /** The cpu average column. */ + CPUAVERAGE("Cpu Avg (ms)", 60, null, TimerDataComparatorEnum.CPUAVERAGE), + /** The cpu minimum column. */ + CPUMIN("Cpu Min (ms)", 60, null, TimerDataComparatorEnum.CPUMIN), + /** The cpu maximum column. */ + CPUMAX("Cpu Max (ms)", 60, null, TimerDataComparatorEnum.CPUMAX), + /** The cpu duration column. */ + CPUDURATION("Cpu Duration (ms)", 70, null, TimerDataComparatorEnum.CPUDURATION); + + /** The name. */ + private String name; + /** The width of the column. */ + private int width; + /** The image descriptor. Can be null */ + private Image image; + /** Comparator for the column. */ + private IDataComparator dataComparator; + + /** + * Default constructor which creates a column enumeration object. + * + * @param name + * The name of the column. + * @param width + * The width of the column. + * @param imageName + * The name of the image. Names are defined in {@link InspectITImages}. + * @param dataComparator + * Comparator for the column. + */ + private Column(String name, int width, String imageName, IDataComparator dataComparator) { + this.name = name; + this.width = width; + this.image = InspectIT.getDefault().getImage(imageName); + this.dataComparator = dataComparator; + } + + /** + * Converts an ordinal into a column. + * + * @param i + * The ordinal. + * @return The appropriate column. + */ + public static Column fromOrd(int i) { + if (i < 0 || i >= Column.values().length) { + throw new IndexOutOfBoundsException("Invalid ordinal"); + } + return Column.values()[i]; + } + + } + + /** + * Defines if correct regular expression is defined in sensor. + */ + private boolean regExEnabledInSensor = false; + + /** + * If the regular expression transformation of the URI is active. + */ + private boolean regExActive = PreferenceId.HttpUriTransformation.DEFAULT; + + /** + * The active HTTP sensor type ident. + */ + private MethodSensorTypeIdent httpSensorTypeIdent; + + /** + * Cached data service. + */ + private ICachedDataService cachedDataService; + + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + cachedDataService = inputDefinition.getRepositoryDefinition().getCachedDataService(); + + if (0 != inputDefinition.getIdDefinition().getSensorTypeId()) { + httpSensorTypeIdent = (MethodSensorTypeIdent) cachedDataService.getSensorTypeIdentForId(inputDefinition.getIdDefinition().getSensorTypeId()); + String regEx = MethodSensorTypeIdentHelper.getRegEx(httpSensorTypeIdent); + if (null != regEx) { + try { + Pattern.compile(regEx); + regExEnabledInSensor = true; + } catch (PatternSyntaxException e) { + InspectIT.getDefault().createInfoDialog( + "The HTTP sensor defines the Regular expression " + regEx + + " for URI transformation that can not be compiled. The transformation option will not be available.\n\n Reason: " + e.getMessage(), -1); + } + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public Set getPreferenceIds() { + Set preferences = super.getPreferenceIds(); + if (regExEnabledInSensor) { + preferences.add(PreferenceId.HTTP_URI_TRANSFORMING); + } + return preferences; + } + + /** + * {@inheritDoc} + */ + @Override + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + super.preferenceEventFired(preferenceEvent); + switch (preferenceEvent.getPreferenceId()) { + case HTTP_URI_TRANSFORMING: + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.HttpUriTransformation.URI_TRANSFORMATION_ACTIVE)) { + regExActive = (Boolean) preferenceEvent.getPreferenceMap().get(PreferenceId.HttpUriTransformation.URI_TRANSFORMATION_ACTIVE); + } + break; + default: + break; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void doRefresh(IProgressMonitor monitor, IRootEditor rootEditor) { + monitor.beginTask("Getting HTTP timer data information", IProgressMonitor.UNKNOWN); + List aggregatedHttpData; + + if (autoUpdate) { + aggregatedHttpData = httptimerDataAccessService.getAggregatedTimerData(template, httpCatorizationOnRequestMethodActive); + } else { + aggregatedHttpData = httptimerDataAccessService.getAggregatedTimerData(template, httpCatorizationOnRequestMethodActive, fromDate, toDate); + } + + if (regExActive && CollectionUtils.isNotEmpty(aggregatedHttpData)) { + AggregationPerformer aggregationPerformer = new AggregationPerformer(new RegExHttpAggregator(httpSensorTypeIdent, httpCatorizationOnRequestMethodActive)); + aggregationPerformer.processCollection(aggregatedHttpData); + aggregatedHttpData = aggregationPerformer.getResultList(); + } + + timerDataList.clear(); + if (CollectionUtils.isNotEmpty(aggregatedHttpData)) { + timerDataList.addAll(aggregatedHttpData); + } + + monitor.done(); + } + + /** + * {@inheritDoc} + */ + @Override + public void createColumns(TableViewer tableViewer) { + for (Column column : Column.values()) { + TableViewerColumn viewerColumn = new TableViewerColumn(tableViewer, SWT.NONE); + viewerColumn.getColumn().setMoveable(true); + viewerColumn.getColumn().setResizable(true); + viewerColumn.getColumn().setText(column.name); + viewerColumn.getColumn().setWidth(column.width); + if (null != column.image) { + viewerColumn.getColumn().setImage(column.image); + } + mapTableViewerColumn(column, viewerColumn); + } + } + + /** + * {@inheritDoc} + */ + @Override + public IBaseLabelProvider getLabelProvider() { + return new StyledCellIndexLabelProvider() { + /** + * {@inheritDoc} + */ + @Override + public StyledString getStyledText(Object element, int index) { + HttpTimerData data = (HttpTimerData) element; + Column enumId = Column.fromOrd(index); + + StyledString styledString = getStyledTextForColumn(data, enumId); + if (addWarnSign(data, enumId)) { + styledString.append(TextFormatter.getWarningSign()); + } + return styledString; + } + + /** + * Decides if the warn sign should be added for the specific column. + * + * @param data + * TimerData + * @param column + * Column to check. + * @return True if warn sign should be added. + */ + private boolean addWarnSign(TimerData data, Column column) { + switch (column) { + case EXCLUSIVEAVERAGE: + case EXCLUSIVEMAX: + case EXCLUSIVEMIN: + case EXCLUSIVESUM: + int affPercentage = (int) (data.getInvocationAffiliationPercentage() * 100); + return data.isExclusiveTimeDataAvailable() && affPercentage < 100; + default: + return false; + } + } + + /** + * + * {@inheritDoc} + */ + @Override + protected Image getColumnImage(Object element, int index) { + HttpTimerData data = (HttpTimerData) element; + Column enumId = Column.fromOrd(index); + + switch (enumId) { + case CHARTING: + if (data.isCharting()) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_CHART_PIE); + } + default: + return super.getColumnImage(element, index); + } + + } + + /** + * {@inheritDoc} + */ + @Override + public String getToolTipText(Object element, int index) { + HttpTimerData data = (HttpTimerData) element; + Column enumId = Column.fromOrd(index); + + switch (enumId) { + case CHARTING: + if (data.isCharting()) { + return "Duration chart can be displayed for this HTTP data."; + } + default: + return super.getToolTipText(element, index); + } + } + }; + } + + /** + * {@inheritDoc} + */ + @Override + public ViewerComparator getComparator() { + ICachedDataService cachedDataService = getInputDefinition().getRepositoryDefinition().getCachedDataService(); + TableViewerComparator httpTimerDataViewerComparator = new TableViewerComparator(); + for (Column column : Column.values()) { + ResultComparator resultComparator; + if (Column.URI.equals(column)) { + resultComparator = new ResultComparator(new UriOrRegExComparator(column.dataComparator), cachedDataService); + } else { + resultComparator = new ResultComparator(column.dataComparator, cachedDataService); + } + httpTimerDataViewerComparator.addColumn(getMappedTableViewerColumn(column).getColumn(), resultComparator); + } + + return httpTimerDataViewerComparator; + } + + /** + * {@inheritDoc} + */ + @Override + public String getReadableString(Object object) { + if (object instanceof HttpTimerData) { + HttpTimerData data = (HttpTimerData) object; + StringBuilder sb = new StringBuilder(); + for (Column column : Column.values()) { + sb.append(getStyledTextForColumn(data, column).toString()); + sb.append('\t'); + } + return sb.toString(); + } else { + throw new RuntimeException("Could not create the human readable string! Class is: " + object.getClass().getCanonicalName()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public List getColumnValues(Object object) { + if (object instanceof HttpTimerData) { + HttpTimerData data = (HttpTimerData) object; + List values = new ArrayList(); + for (Column column : Column.values()) { + values.add(getStyledTextForColumn(data, column).toString()); + } + return values; + } + throw new RuntimeException("Could not create the column values!"); + } + + /** + * Returns the styled text for a specific column. + * + * @param data + * The data object to extract the information from. + * @param enumId + * The enumeration ID. + * @return The styled string containing the information from the data object. + */ + private StyledString getStyledTextForColumn(HttpTimerData data, Column enumId) { + switch (enumId) { + case CHARTING: + return emptyStyledString; + case URI: + if (data instanceof RegExAggregatedHttpTimerData) { + return new StyledString(((RegExAggregatedHttpTimerData) data).getTransformedUri()); + } else { + return new StyledString(data.getUri()); + } + case HTTP_METHOD: + return new StyledString(data.getRequestMethod()); + case INVOCATION_AFFILLIATION: + int percentage = (int) (data.getInvocationAffiliationPercentage() * 100); + int invocations = 0; + if (null != data.getInvocationParentsIdSet()) { + invocations = data.getInvocationParentsIdSet().size(); + } + return TextFormatter.getInvocationAffilliationPercentageString(percentage, invocations); + case COUNT: + return new StyledString(String.valueOf(data.getCount())); + case AVERAGE: + if (data.isTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getAverage(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case MIN: + if (data.isTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getMin(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case MAX: + if (data.isTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getMax(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case DURATION: + if (data.isTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getDuration(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case CPUAVERAGE: + if (data.isCpuMetricDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getCpuAverage(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case CPUMIN: + if (data.isCpuMetricDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getCpuMin(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case CPUMAX: + if (data.isCpuMetricDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getCpuMax(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case CPUDURATION: + if (data.isCpuMetricDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getCpuDuration(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case EXCLUSIVEAVERAGE: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveAverage(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case EXCLUSIVEMAX: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveMax(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case EXCLUSIVEMIN: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveMin(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case EXCLUSIVESUM: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveDuration(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + default: + return new StyledString("error"); + } + } + + /** + * The RegEx aggregator. + * + * @author Ivan Senic + * + */ + @SuppressWarnings("serial") + private static final class RegExHttpAggregator extends HttpTimerDataAggregator { + + /** + * HTTP sensor type ident. + */ + private MethodSensorTypeIdent httpSensorTypeIdent; + + /** + * Default constructor. + * + * @param httpSensorTypeIdent + * HTTP sensor type ident. + * @param includeRequestMethod + * If request method should be included. + */ + public RegExHttpAggregator(MethodSensorTypeIdent httpSensorTypeIdent, boolean includeRequestMethod) { + super(true, includeRequestMethod); + this.httpSensorTypeIdent = httpSensorTypeIdent; + } + + /** + * {@inheritDoc} + */ + @Override + public void aggregate(IAggregatedData aggregatedObject, HttpTimerData objectToAdd) { + super.aggregate(aggregatedObject, objectToAdd); + ((RegExAggregatedHttpTimerData) aggregatedObject).getAggregatedDataList().add(objectToAdd); + } + + /** + * {@inheritDoc} + */ + @Override + public IAggregatedData getClone(HttpTimerData httpData) { + RegExAggregatedHttpTimerData clone = new RegExAggregatedHttpTimerData(); + clone.setPlatformIdent(httpData.getPlatformIdent()); + clone.setSensorTypeIdent(httpData.getSensorTypeIdent()); + clone.setMethodIdent(httpData.getMethodIdent()); + clone.setCharting(httpData.isCharting()); + clone.setRequestMethod(httpData.getRequestMethod()); + clone.setTransformedUri(RegExAggregatedHttpTimerData.getTransformedUri(httpData, httpSensorTypeIdent)); + return clone; + } + + /** + * {@inheritDoc} + */ + @Override + public Object getAggregationKey(HttpTimerData httpData) { + final int prime = 31; + int result = 0; + String transformed = RegExAggregatedHttpTimerData.getTransformedUri(httpData, httpSensorTypeIdent); + result = prime * result + ((transformed == null) ? 0 : transformed.hashCode()); + + if (includeRequestMethod) { + result = prime * result + ((httpData.getRequestMethod() == null) ? 0 : httpData.getRequestMethod().hashCode()); + } + return result; + } + + } + + /** + * Comparator that is needed for the column where URI or regular expression transformation can + * be displayed. + * + * @author Ivan Senic + */ + private static final class UriOrRegExComparator implements IDataComparator { + + /** + * The comparator that will be used if reg ex is not active. + */ + private final IDataComparator comparator; + + /** + * @param dataComparator + * The comparator that will be used if reg ex is not active. + */ + public UriOrRegExComparator(IDataComparator dataComparator) { + this.comparator = dataComparator; + } + + /** + * {@inheritDoc} + */ + @Override + public int compare(HttpTimerData o1, HttpTimerData o2, ICachedDataService cachedDataService) { + if (o1 instanceof RegExAggregatedHttpTimerData && o2 instanceof RegExAggregatedHttpTimerData) { + return ((RegExAggregatedHttpTimerData) o1).getTransformedUri().compareToIgnoreCase(((RegExAggregatedHttpTimerData) o2).getTransformedUri()); + } else { + return comparator.compare(o1, o2, cachedDataService); + } + } + + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/InvocOverviewInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/InvocOverviewInputController.java new file mode 100644 index 000000000..dd184314f --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/InvocOverviewInputController.java @@ -0,0 +1,667 @@ +package info.novatec.inspectit.rcp.editor.table.input; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.service.ICachedDataService; +import info.novatec.inspectit.cmr.service.IInvocationDataAccessService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.comparator.DefaultDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.IDataComparator; +import info.novatec.inspectit.communication.comparator.InvocationSequenceDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.MethodSensorDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.InvocationSequenceDataHelper; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition.IdDefinition; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId.LiveMode; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; +import info.novatec.inspectit.rcp.editor.table.RemoteTableViewerComparator; +import info.novatec.inspectit.rcp.editor.tooltip.IColumnToolTipProvider; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.ImageFormatter; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; +import info.novatec.inspectit.rcp.preferences.PreferencesUtils; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Date; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; + +/** + * This input controller displays an overview of {@link InvocationSequenceData} objects. + * + * @author Patrice Bouillet + * + */ +public class InvocOverviewInputController extends AbstractTableInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.table.invocoverview"; + + /** + * The private inner enumeration used to define the used IDs which are mapped into the columns. + * The order in this enumeration represents the order of the columns. If it is reordered, + * nothing else has to be changed. + * + * @author Patrice Bouillet + * + */ + protected static enum Column { + /** The time column. */ + NESTED_DATA("Nested Data", 40, null, InvocationSequenceDataComparatorEnum.NESTED_DATA), + /** The time column. */ + TIME("Start Time", 150, InspectITImages.IMG_TIMESTAMP, DefaultDataComparatorEnum.TIMESTAMP), + /** The method column. */ + METHOD("Method", 550, InspectITImages.IMG_METHOD, MethodSensorDataComparatorEnum.METHOD), + /** The duration column. */ + DURATION("Duration (ms)", 100, InspectITImages.IMG_TIME, InvocationSequenceDataComparatorEnum.DURATION), + /** The count column. */ + COUNT("Child Count", 100, null, InvocationSequenceDataComparatorEnum.CHILD_COUNT), + /** The URI column. */ + URI("URI", 150, null, InvocationSequenceDataComparatorEnum.URI), + /** The Use case column. */ + USE_CASE("Use case", 100, null, InvocationSequenceDataComparatorEnum.USE_CASE); + + /** The name. */ + private String name; + /** The width of the column. */ + private int width; + /** The image descriptor. Can be null */ + private Image image; + /** Comparator for the column. */ + protected IDataComparator dataComparator; + + /** + * Default constructor which creates a column enumeration object. + * + * @param name + * The name of the column. + * @param width + * The width of the column. + * @param imageName + * The name of the image. Names are defined in {@link InspectITImages}. + * @param dataComparator + * Comparator for the column. + */ + private Column(String name, int width, String imageName, IDataComparator dataComparator) { + this.name = name; + this.width = width; + this.image = InspectIT.getDefault().getImage(imageName); + this.dataComparator = dataComparator; + } + + /** + * Converts an ordinal into a column. + * + * @param i + * The ordinal. + * @return The appropriate column. + */ + public static Column fromOrd(int i) { + if (i < 0 || i >= Column.values().length) { + throw new IndexOutOfBoundsException("Invalid ordinal"); + } + return Column.values()[i]; + } + + } + + /** + * Default comparator when no sorting is defined. + */ + private final ResultComparator defaultComparator = new ResultComparator<>(DefaultDataComparatorEnum.TIMESTAMP, false); + + /** + * The template object which is send to the server. + */ + private InvocationSequenceData template; + + /** + * The list of invocation sequence data objects which is displayed. + */ + private List invocationSequenceData = new ArrayList(); + + /** + * The limit of the result set. + */ + private int limit = PreferencesUtils.getIntValue(PreferencesConstants.ITEMS_COUNT_TO_SHOW); + + /** + * The used data access service to access the data on the CMR. + */ + private IInvocationDataAccessService dataAccessService; + + /** + * The cached service is needed because of the ID mappings. + */ + private ICachedDataService cachedDataService; + + /** + * Date to display invocations from. + */ + private Date fromDate = null; + + /** + * Date to display invocations to. + */ + private Date toDate = null; + + /** + * Are we in live mode. + */ + private boolean autoUpdate = LiveMode.ACTIVE_DEFAULT; + + /** + * Empty styled string. + */ + private final StyledString emptyStyledString = new StyledString(); + + /** + * The resource manager is used for the images etc. + */ + private LocalResourceManager resourceManager = new LocalResourceManager(JFaceResources.getResources()); + + /** + * Result comparator to be used on the server. + */ + private ResultComparator resultComparator = defaultComparator; + + /** + * + * @return Returns list of invocation sequence data that represents a table input. + */ + protected List getInvocationSequenceData() { + return invocationSequenceData; + } + + /** + * Returns data access service for retrieving the data from the server. + * + * @return Returns data access service. + */ + protected IInvocationDataAccessService getDataAccessService() { + return dataAccessService; + } + + /** + * Gets {@link #cachedDataService}. + * + * @return {@link #cachedDataService} + */ + protected ICachedDataService getCachedDataService() { + return cachedDataService; + } + + /** + * Returns current view item count limit defined for the view. + * + * @return Returns current view item count limit. + */ + protected int getLimit() { + return limit; + } + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + template = new InvocationSequenceData(); + template.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); + template.setSensorTypeIdent(inputDefinition.getIdDefinition().getSensorTypeId()); + template.setMethodIdent(inputDefinition.getIdDefinition().getMethodId()); + + dataAccessService = inputDefinition.getRepositoryDefinition().getInvocationDataAccessService(); + cachedDataService = inputDefinition.getRepositoryDefinition().getCachedDataService(); + } + + /** + * {@inheritDoc} + */ + public void createColumns(TableViewer tableViewer) { + for (Column column : Column.values()) { + TableViewerColumn viewerColumn = new TableViewerColumn(tableViewer, SWT.NONE); + viewerColumn.getColumn().setMoveable(true); + viewerColumn.getColumn().setResizable(true); + viewerColumn.getColumn().setText(column.name); + viewerColumn.getColumn().setWidth(column.width); + if (null != column.image) { + viewerColumn.getColumn().setImage(column.image); + } + mapTableViewerColumn(column, viewerColumn); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Object getTableInput() { + // this list will be filled with data + return invocationSequenceData; + } + + /** + * {@inheritDoc} + */ + public IContentProvider getContentProvider() { + return new InvocOverviewContentProvider(); + } + + /** + * {@inheritDoc} + */ + public IBaseLabelProvider getLabelProvider() { + return new InvocOverviewLabelProvider(); + } + + /** + * {@inheritDoc} + */ + public ViewerComparator getComparator() { + RemoteTableViewerComparator invocOverviewViewerComparator = new RemoteTableViewerComparator() { + @Override + protected void sortRemotely(ResultComparator resultComparator) { + if (null != resultComparator) { + InvocOverviewInputController.this.resultComparator = resultComparator; + } else { + InvocOverviewInputController.this.resultComparator = defaultComparator; + } + loadDataFromService(); + } + }; + for (Column column : Column.values()) { + // since it is remote sorting we do not provide local cached data service + ResultComparator resultComparator = new ResultComparator(column.dataComparator); + invocOverviewViewerComparator.addColumn(getMappedTableViewerColumn(column).getColumn(), resultComparator); + } + + return invocOverviewViewerComparator; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getPreferenceIds() { + Set preferences = EnumSet.noneOf(PreferenceId.class); + if (getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) { + preferences.add(PreferenceId.CLEAR_BUFFER); + preferences.add(PreferenceId.LIVEMODE); + } + preferences.add(PreferenceId.UPDATE); + preferences.add(PreferenceId.ITEMCOUNT); + preferences.add(PreferenceId.TIMELINE); + return preferences; + } + + /** + * {@inheritDoc} + */ + @Override + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + switch (preferenceEvent.getPreferenceId()) { + case TIMELINE: + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeLine.FROM_DATE_ID)) { + fromDate = (Date) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeLine.FROM_DATE_ID); + } + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeLine.TO_DATE_ID)) { + toDate = (Date) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeLine.TO_DATE_ID); + } + break; + case LIVEMODE: + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.LiveMode.BUTTON_LIVE_ID)) { + autoUpdate = (Boolean) preferenceEvent.getPreferenceMap().get(PreferenceId.LiveMode.BUTTON_LIVE_ID); + } + break; + default: + break; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canOpenInput(List data) { + if (data.isEmpty()) { + return true; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void setLimit(int limit) { + this.limit = limit; + } + + /** + * {@inheritDoc} + */ + @Override + public void doRefresh(IProgressMonitor monitor, IRootEditor rootEditor) { + monitor.beginTask("Updating Invocation Overview", IProgressMonitor.UNKNOWN); + monitor.subTask("Retrieving the Invocation Overview"); + + loadDataFromService(); + + monitor.done(); + } + + /** + * Reloads the data from the service. + */ + private void loadDataFromService() { + List invocData; + + if (!autoUpdate) { + if (template.getMethodIdent() != IdDefinition.ID_NOT_USED) { + invocData = dataAccessService.getInvocationSequenceOverview(template.getPlatformIdent(), template.getMethodIdent(), limit, fromDate, toDate, resultComparator); + } else { + invocData = dataAccessService.getInvocationSequenceOverview(template.getPlatformIdent(), limit, fromDate, toDate, resultComparator); + } + } else { + if (template.getMethodIdent() != IdDefinition.ID_NOT_USED) { + invocData = dataAccessService.getInvocationSequenceOverview(template.getPlatformIdent(), template.getMethodIdent(), limit, resultComparator); + } else { + invocData = dataAccessService.getInvocationSequenceOverview(template.getPlatformIdent(), limit, resultComparator); + } + } + + // why this? so only update with new data if returned collection is not empty, i would say + // with every update, if it is empty, then there is nothing to display + // then i also done need the clearInvocationFlag + // I changed here, .clear() is now out of if clause + + invocationSequenceData.clear(); + if (!invocData.isEmpty()) { + invocationSequenceData.addAll(invocData); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void doubleClick(DoubleClickEvent event) { + final StructuredSelection selection = (StructuredSelection) event.getSelection(); + if (!selection.isEmpty()) { + try { + PlatformUI.getWorkbench().getProgressService().busyCursorWhile(new IRunnableWithProgress() { + public void run(final IProgressMonitor monitor) { + monitor.beginTask("Retrieving Invocation detail data", IProgressMonitor.UNKNOWN); + InvocationSequenceData invocationSequenceData = (InvocationSequenceData) selection.getFirstElement(); + InvocationSequenceData data = (InvocationSequenceData) dataAccessService.getInvocationSequenceDetail(invocationSequenceData); + final List invocationSequenceDataList = new ArrayList(); + invocationSequenceDataList.add(data); + Display.getDefault().asyncExec(new Runnable() { + public void run() { + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + IWorkbenchPage page = window.getActivePage(); + IRootEditor rootEditor = (IRootEditor) page.getActiveEditor(); + rootEditor.setDataInput(invocationSequenceDataList); + } + }); + monitor.done(); + } + }); + } catch (InvocationTargetException e) { + MessageDialog.openError(Display.getDefault().getActiveShell().getShell(), "Error", e.getCause().toString()); + } catch (InterruptedException e) { + MessageDialog.openInformation(Display.getDefault().getActiveShell().getShell(), "Cancelled", e.getCause().toString()); + } + } + } + + /** + * The label provider for this view. + * + * @author Patrice Bouillet + * + */ + private final class InvocOverviewLabelProvider extends StyledCellIndexLabelProvider implements IColumnToolTipProvider { + + /** + * {@inheritDoc} + */ + @Override + protected StyledString getStyledText(Object element, int index) { + InvocationSequenceData data = (InvocationSequenceData) element; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + Column enumId = Column.fromOrd(index); + + return getStyledTextForColumn(data, methodIdent, enumId); + } + + /** + * + * {@inheritDoc} + */ + @Override + protected Image getColumnImage(Object element, int index) { + InvocationSequenceData data = (InvocationSequenceData) element; + Column enumId = Column.fromOrd(index); + + switch (enumId) { + case NESTED_DATA: + if (InvocationSequenceDataHelper.hasNestedSqlStatements(data) && InvocationSequenceDataHelper.hasNestedExceptions(data)) { + return ImageFormatter.getCombinedImage(resourceManager, SWT.HORIZONTAL, InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_DATABASE), InspectIT.getDefault() + .getImageDescriptor(InspectITImages.IMG_EXCEPTION_SENSOR)); + } else if (InvocationSequenceDataHelper.hasNestedSqlStatements(data)) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_DATABASE); + } else if (InvocationSequenceDataHelper.hasNestedExceptions(data)) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_EXCEPTION_SENSOR); + } else { + return super.getColumnImage(element, index); + } + default: + return super.getColumnImage(element, index); + } + + } + + /** + * {@inheritDoc} + */ + @Override + public String getToolTipText(Object element, int index) { + InvocationSequenceData data = (InvocationSequenceData) element; + Column enumId = Column.fromOrd(index); + switch (enumId) { + case NESTED_DATA: + if (InvocationSequenceDataHelper.hasNestedSqlStatements(data) || InvocationSequenceDataHelper.hasNestedExceptions(data)) { + StringBuilder toolTip = new StringBuilder("This invocation contains:"); + if (InvocationSequenceDataHelper.hasNestedSqlStatements(data)) { + toolTip.append("\n - SQL statement(s)"); + } + if (InvocationSequenceDataHelper.hasNestedExceptions(data)) { + toolTip.append("\n - Exception(s)"); + } + return toolTip.toString(); + } else { + return super.getToolTipText(element, index); + } + default: + return null; + } + } + } + + /** + * The content provider for this view. + * + * @author Patrice Bouillet + * + */ + private static final class InvocOverviewContentProvider implements IStructuredContentProvider { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public Object[] getElements(Object inputElement) { + List invocationSequenceData = (List) inputElement; + return invocationSequenceData.toArray(); + } + + /** + * {@inheritDoc} + */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + + } + + /** + * Returns the styled text for a specific column. + * + * @param data + * The data object to extract the information from. + * @param methodIdent + * The method ident object. + * @param enumId + * The enumeration ID. + * @return The styled string containing the information from the data object. + */ + private StyledString getStyledTextForColumn(InvocationSequenceData data, MethodIdent methodIdent, Column enumId) { + switch (enumId) { + case NESTED_DATA: + return emptyStyledString; + case TIME: + return new StyledString(NumberFormatter.formatTimeWithMillis(data.getTimeStamp())); + case METHOD: + return TextFormatter.getStyledMethodString(methodIdent); + case DURATION: + if (InvocationSequenceDataHelper.hasTimerData(data)) { + return new StyledString(NumberFormatter.formatDouble(data.getTimerData().getDuration())); + } else { + // this duration is always available but could differ from + // the timer data duration as these measures are taken + // separately. + return new StyledString(NumberFormatter.formatDouble(data.getDuration())); + } + case COUNT: + return new StyledString(NumberFormatter.formatLong(data.getChildCount())); + case URI: + if (InvocationSequenceDataHelper.hasHttpTimerData(data)) { + String uri = ((HttpTimerData) data.getTimerData()).getUri(); + if (null != uri) { + return new StyledString(uri); + } else { + return emptyStyledString; + } + } else { + return emptyStyledString; + } + case USE_CASE: + if (InvocationSequenceDataHelper.hasHttpTimerData(data)) { + String useCase = ((HttpTimerData) data.getTimerData()).getInspectItTaggingHeaderValue(); + if (null != useCase) { + return new StyledString(useCase); + } else { + return emptyStyledString; + } + } else { + return emptyStyledString; + } + default: + return new StyledString("error"); + } + } + + /** + * {@inheritDoc} + */ + public String getReadableString(Object object) { + if (object instanceof InvocationSequenceData) { + InvocationSequenceData data = (InvocationSequenceData) object; + StringBuilder sb = new StringBuilder(); + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + for (Column column : Column.values()) { + sb.append(getStyledTextForColumn(data, methodIdent, column).toString()); + sb.append('\t'); + } + return sb.toString(); + } + throw new RuntimeException("Could not create the human readable string!"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getColumnValues(Object object) { + if (object instanceof InvocationSequenceData) { + InvocationSequenceData data = (InvocationSequenceData) object; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + List values = new ArrayList(); + for (Column column : Column.values()) { + values.add(getStyledTextForColumn(data, methodIdent, column).toString()); + } + return values; + } + throw new RuntimeException("Could not create the column values!"); + } + + /** + * Gets {@link #resultComparator}. + * + * @return {@link #resultComparator} + */ + public ResultComparator getResultComparator() { + return resultComparator; + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + resourceManager.dispose(); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/MethodInvocInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/MethodInvocInputController.java new file mode 100644 index 000000000..f3318ba87 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/MethodInvocInputController.java @@ -0,0 +1,659 @@ +package info.novatec.inspectit.rcp.editor.table.input; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.service.ICachedDataService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.comparator.DefaultDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.IDataComparator; +import info.novatec.inspectit.communication.comparator.MethodSensorDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.TimerDataComparatorEnum; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.indexing.aggregation.impl.AggregationPerformer; +import info.novatec.inspectit.indexing.aggregation.impl.TimerDataAggregator; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.IPreferenceGroup; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.table.TableViewerComparator; +import info.novatec.inspectit.rcp.editor.viewers.RawAggregatedResultComparator; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.handlers.ShowHideColumnsHandler; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.TableColumn; + +/** + * This input controller displays details of all methods involved in an invocation sequence. + * + * @author Patrice Bouillet + * + */ +public class MethodInvocInputController extends AbstractTableInputController { + + /** + * The private inner enumeration used to define the used IDs which are mapped into the columns. + * The order in this enumeration represents the order of the columns. If it is reordered, + * nothing else has to be changed. + * + * @author Patrice Bouillet + * + */ + private static enum Column { + /** The timestamp column. */ + TIMESTAMP("Timestamp", 130, InspectITImages.IMG_TIMESTAMP, false, true, DefaultDataComparatorEnum.TIMESTAMP), + /** The package column. */ + PACKAGE("Package", 200, InspectITImages.IMG_PACKAGE, true, true, MethodSensorDataComparatorEnum.PACKAGE), + /** The class column. */ + CLASS("Class", 200, InspectITImages.IMG_CLASS, true, true, MethodSensorDataComparatorEnum.CLASS), + /** The method column. */ + METHOD("Method", 300, InspectITImages.IMG_METHOD, true, true, MethodSensorDataComparatorEnum.METHOD), + /** The count column. */ + COUNT("Count", 60, null, true, false, TimerDataComparatorEnum.COUNT), + /** The average column. */ + AVERAGE("Avg (ms)", 60, null, true, false, TimerDataComparatorEnum.AVERAGE), + /** The minimum column. */ + MIN("Min (ms)", 60, null, true, false, TimerDataComparatorEnum.MIN), + /** The maximum column. */ + MAX("Max (ms)", 60, null, true, false, TimerDataComparatorEnum.MAX), + /** The duration column. */ + DURATION("Duration (ms)", 70, null, true, true, TimerDataComparatorEnum.DURATION), + /** The average exclusive duration column. */ + EXCLUSIVEAVERAGE("Exc. Avg (ms)", 80, null, true, false, TimerDataComparatorEnum.EXCLUSIVEAVERAGE), + /** The min exclusive duration column. */ + EXCLUSIVEMIN("Exc. Min (ms)", 80, null, true, false, TimerDataComparatorEnum.EXCLUSIVEMIN), + /** The max exclusive duration column. */ + EXCLUSIVEMAX("Exc. Max (ms)", 80, null, true, false, TimerDataComparatorEnum.EXCLUSIVEMAX), + /** The total exclusive duration column. */ + EXCLUSIVESUM("Exc. duration (ms)", 80, null, true, true, TimerDataComparatorEnum.EXCLUSIVEDURATION), + /** The cpu average column. */ + CPUAVERAGE("Cpu Avg (ms)", 60, null, true, false, TimerDataComparatorEnum.CPUAVERAGE), + /** The cpu minimum column. */ + CPUMIN("Cpu Min (ms)", 60, null, true, false, TimerDataComparatorEnum.CPUMIN), + /** The cpu maximum column. */ + CPUMAX("Cpu Max (ms)", 60, null, true, false, TimerDataComparatorEnum.CPUMAX), + /** The cpu duration column. */ + CPUDURATION("Cpu Duration (ms)", 70, null, true, true, TimerDataComparatorEnum.CPUDURATION); + + /** The name. */ + private String name; + /** The width of the column. */ + private int width; + /** The image descriptor. Can be null */ + private Image image; + /** If the column should be shown in aggregated mode. */ + private boolean showInAggregatedMode; + /** If the column should be shown in raw mode. */ + private boolean showInRawMode; + /** Comparator for the column. */ + private IDataComparator dataComparator; + + /** + * Default constructor which creates a column enumeration object. + * + * @param name + * The name of the column. + * @param width + * The width of the column. + * @param imageName + * The name of the image. Names are defined in {@link InspectITImages}. + * @param showInAggregatedMode + * If the column should be shown in aggregated mode. + * @param showInRawMode + * If the column should be shown in raw mode. + * @param dataComparator + * Comparator for the column. + * + */ + private Column(String name, int width, String imageName, boolean showInAggregatedMode, boolean showInRawMode, IDataComparator dataComparator) { + this.name = name; + this.width = width; + this.image = InspectIT.getDefault().getImage(imageName); + this.showInAggregatedMode = showInAggregatedMode; + this.showInRawMode = showInRawMode; + this.dataComparator = dataComparator; + } + + /** + * Converts an ordinal into a column. + * + * @param i + * The ordinal. + * @return The appropriate column. + */ + public static Column fromOrd(int i) { + if (i < 0 || i >= Column.values().length) { + throw new IndexOutOfBoundsException("Invalid ordinal"); + } + return Column.values()[i]; + } + } + + /** + * The cached service is needed because of the ID mappings. + */ + private ICachedDataService cachedDataService; + + /** + * Empty styled string. + */ + private final StyledString emptyStyledString = new StyledString(); + + /** + * List that is displayed after processing the invocation. + */ + private List timerDataList; + + /** + * Should view display raw mode or not. + */ + private boolean rawMode = false; + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + cachedDataService = inputDefinition.getRepositoryDefinition().getCachedDataService(); + } + + /** + * {@inheritDoc} + */ + public void createColumns(TableViewer tableViewer) { + for (Column column : Column.values()) { + TableViewerColumn viewerColumn = new TableViewerColumn(tableViewer, SWT.NONE); + viewerColumn.getColumn().setMoveable(true); + viewerColumn.getColumn().setResizable(true); + viewerColumn.getColumn().setText(column.name); + if (column.showInAggregatedMode) { + viewerColumn.getColumn().setWidth(column.width); + } else { + viewerColumn.getColumn().setWidth(0); + } + if (null != column.image) { + viewerColumn.getColumn().setImage(column.image); + } + mapTableViewerColumn(column, viewerColumn); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canAlterColumnWidth(TableColumn tableColumn) { + for (Column column : Column.values()) { + if (Objects.equals(getMappedTableViewerColumn(column).getColumn(), tableColumn)) { + return (column.showInRawMode && rawMode) || (column.showInAggregatedMode && !rawMode); + } + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getPreferenceIds() { + Set preferences = EnumSet.noneOf(PreferenceId.class); + preferences.add(PreferenceId.INVOCATION_SUBVIEW_MODE); + return preferences; + } + + /** + * {@inheritDoc} + */ + @Override + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + if (PreferenceId.INVOCATION_SUBVIEW_MODE.equals(preferenceEvent.getPreferenceId())) { + Map preferenceMap = preferenceEvent.getPreferenceMap(); + if (null != preferenceMap && preferenceMap.containsKey(PreferenceId.InvocationSubviewMode.RAW)) { + Boolean isRawMode = (Boolean) preferenceMap.get(PreferenceId.InvocationSubviewMode.RAW); + + // first show/hide columns and then change the rawMode value + handleRawAggregatedColumnVisibility(isRawMode.booleanValue()); + rawMode = isRawMode.booleanValue(); + } + } + } + + /** + * Handles the raw and aggregated columns hiding/showing. + * + * @param rawMode + * Is raw mode active. + */ + private void handleRawAggregatedColumnVisibility(boolean rawMode) { + for (Column column : Column.values()) { + if (rawMode) { + if (column.showInRawMode && !column.showInAggregatedMode && !ShowHideColumnsHandler.isColumnHidden(this.getClass(), column.name)) { + Integer width = ShowHideColumnsHandler.getRememberedColumnWidth(this.getClass(), column.name); + getMappedTableViewerColumn(column).getColumn().setWidth((null != width) ? width.intValue() : column.width); + } else if (!column.showInRawMode && column.showInAggregatedMode) { + getMappedTableViewerColumn(column).getColumn().setWidth(0); + } + } else { + if (!column.showInRawMode && column.showInAggregatedMode && !ShowHideColumnsHandler.isColumnHidden(this.getClass(), column.name)) { + Integer width = ShowHideColumnsHandler.getRememberedColumnWidth(this.getClass(), column.name); + getMappedTableViewerColumn(column).getColumn().setWidth((null != width) ? width.intValue() : column.width); + } else if (column.showInRawMode && !column.showInAggregatedMode) { + getMappedTableViewerColumn(column).getColumn().setWidth(0); + } + } + } + } + + /** + * {@inheritDoc} + */ + public IContentProvider getContentProvider() { + return new MethodInvocContentProvider(); + } + + /** + * {@inheritDoc} + */ + public ViewerComparator getComparator() { + TableViewerComparator methodInputViewerComparator = new TableViewerComparator(); + for (Column column : Column.values()) { + RawAggregatedResultComparator comparator = new RawAggregatedResultComparator(column.dataComparator, cachedDataService, column.showInRawMode, + column.showInAggregatedMode) { + @Override + protected boolean isRawMode() { + return rawMode; + } + }; + methodInputViewerComparator.addColumn(getMappedTableViewerColumn(column).getColumn(), comparator); + } + + return methodInputViewerComparator; + } + + /** + * {@inheritDoc} + */ + public IBaseLabelProvider getLabelProvider() { + return new MethodInvocLabelProvider(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canOpenInput(List data) { + if (null == data) { + return false; + } + + if (data.isEmpty()) { + return true; + } + + if (!(data.get(0) instanceof InvocationSequenceData)) { + return false; + } + + return true; + } + + /** + * The content provider for this view. + * + * @author Patrice Bouillet + * + */ + private final class MethodInvocContentProvider implements IStructuredContentProvider { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public Object[] getElements(Object inputElement) { + List invocationSequenceDataList = (List) inputElement; + timerDataList = getRawInputList(invocationSequenceDataList, new ArrayList()); + if (!rawMode) { + AggregationPerformer aggregationPerformer = new AggregationPerformer(new TimerDataAggregator()); + aggregationPerformer.processCollection(timerDataList); + timerDataList = aggregationPerformer.getResultList(); + } else { + Collections.sort(timerDataList, new Comparator() { + @Override + public int compare(TimerData o1, TimerData o2) { + return o1.getTimeStamp().compareTo(o2.getTimeStamp()); + } + }); + } + return timerDataList.toArray(); + } + + /** + * Creates the raw input list of timers from a list of invocations. + * + * @param invocationSequenceDataList + * List of invocations to check. + * @param resultList + * List where results will be stored. Needed because of reflection. Note that + * this list will be returned as the result. + * @return List of raw order timer data. + */ + public List getRawInputList(List invocationSequenceDataList, List resultList) { + for (InvocationSequenceData invocationSequenceData : invocationSequenceDataList) { + TimerData timerData = getTimerData(invocationSequenceData); + if (null != timerData) { + resultList.add(timerData); + } + + getRawInputList(invocationSequenceData.getNestedSequences(), resultList); + } + + return resultList; + } + + /** + * Returns the extracted timer data from the invocation. + * + * @param invocationData + * {@link InvocationSequenceData}. + * @return Timer data or null if it can not be created. + */ + private TimerData getTimerData(InvocationSequenceData invocationData) { + TimerData timerData = null; + if (null != invocationData.getTimerData()) { + timerData = invocationData.getTimerData(); + } else if (null != invocationData.getSqlStatementData()) { + timerData = invocationData.getSqlStatementData(); + } else if (null == invocationData.getParentSequence()) { + timerData = createTimerDataForRootInvocation(invocationData); + } + return timerData; + } + + /** + * Creates the timer data from a root invocation object. + * + * @param invocationData + * Root invocation object. + * @return Timer data with set duration from the invocation and calculated exclusive + * duration. + */ + private TimerData createTimerDataForRootInvocation(InvocationSequenceData invocationData) { + TimerData timerData = new TimerData(); + timerData.setPlatformIdent(invocationData.getPlatformIdent()); + timerData.setMethodIdent(invocationData.getMethodIdent()); + timerData.setTimeStamp(invocationData.getTimeStamp()); + timerData.setDuration(invocationData.getDuration()); + timerData.calculateMax(invocationData.getDuration()); + timerData.calculateMin(invocationData.getDuration()); + timerData.increaseCount(); + double exclusiveTime = invocationData.getDuration() - computeNestedDuration(invocationData); + timerData.setExclusiveDuration(exclusiveTime); + timerData.calculateExclusiveMax(exclusiveTime); + timerData.calculateExclusiveMin(exclusiveTime); + timerData.increaseExclusiveCount(); + timerData.finalizeData(); + return timerData; + } + + /** + * Computes the duration of the nested invocation elements. + * + * @param data + * The data objects which is inspected for its nested elements. + * @return The duration of all nested sequences (with their nested sequences as well). + */ + private double computeNestedDuration(InvocationSequenceData data) { + if (data.getNestedSequences().isEmpty()) { + return 0; + } + + double nestedDuration = 0d; + boolean added = false; + for (InvocationSequenceData nestedData : (List) data.getNestedSequences()) { + if (null == nestedData.getParentSequence()) { + nestedDuration = nestedDuration + nestedData.getDuration(); + added = true; + } else if (null != nestedData.getTimerData()) { + nestedDuration = nestedDuration + nestedData.getTimerData().getDuration(); + added = true; + } else if (null != nestedData.getSqlStatementData() && 1 == nestedData.getSqlStatementData().getCount()) { + nestedDuration = nestedDuration + nestedData.getSqlStatementData().getDuration(); + added = true; + } + if (!added && !nestedData.getNestedSequences().isEmpty()) { + // nothing was added, but there could be child elements with + // time measurements + nestedDuration = nestedDuration + computeNestedDuration(nestedData); + } + added = false; + } + + return nestedDuration; + } + + /** + * {@inheritDoc} + */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + + } + + /** + * The sql label provider used by this view. + * + * @author Patrice Bouillet + * + */ + private final class MethodInvocLabelProvider extends StyledCellIndexLabelProvider { + + /** + * Creates the styled text. + * + * @param element + * The element to create the styled text for. + * @param index + * The index in the column. + * @return The created styled string. + */ + @Override + public StyledString getStyledText(Object element, int index) { + TimerData data = (TimerData) element; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + Column enumId = Column.fromOrd(index); + + return getStyledTextForColumn(data, methodIdent, enumId); + } + } + + /** + * Returns the styled text for a specific column. + * + * @param data + * The data object to extract the information from. + * @param methodIdent + * The method ident object. + * @param enumId + * The enumeration ID. + * @return The styled string containing the information from the data object. + */ + private StyledString getStyledTextForColumn(TimerData data, MethodIdent methodIdent, Column enumId) { + switch (enumId) { + case TIMESTAMP: + if (rawMode) { + return new StyledString(NumberFormatter.formatTimeWithMillis(data.getTimeStamp())); + } else { + return emptyStyledString; + } + case PACKAGE: + if (methodIdent.getPackageName() != null && !methodIdent.getPackageName().equals("")) { + return new StyledString(methodIdent.getPackageName()); + } else { + return new StyledString("(default)"); + } + case CLASS: + return new StyledString(methodIdent.getClassName()); + case METHOD: + return new StyledString(TextFormatter.getMethodWithParameters(methodIdent)); + case COUNT: + return new StyledString(String.valueOf(data.getCount())); + case AVERAGE: + // check if it is a valid data (or if timer data was available) + if (data.isTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getAverage())); + } else { + return emptyStyledString; + } + case MIN: + if (data.isTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getMin())); + } else { + return emptyStyledString; + } + case MAX: + if (data.isTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getMax())); + } else { + return emptyStyledString; + } + case DURATION: + if (data.isTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getDuration())); + } else { + return emptyStyledString; + } + case CPUAVERAGE: + // check if it is a valid data (or if timer data was available) + if (data.isCpuMetricDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getCpuAverage())); + } else { + return emptyStyledString; + } + case CPUMIN: + if (data.isCpuMetricDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getCpuMin())); + } else { + return emptyStyledString; + } + case CPUMAX: + if (data.isCpuMetricDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getCpuMax())); + } else { + return emptyStyledString; + } + case CPUDURATION: + if (data.isCpuMetricDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getCpuDuration())); + } else { + return emptyStyledString; + } + case EXCLUSIVESUM: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveDuration())); + } else { + return emptyStyledString; + } + case EXCLUSIVEAVERAGE: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveAverage())); + } else { + return emptyStyledString; + } + case EXCLUSIVEMIN: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveMin())); + } else { + return emptyStyledString; + } + case EXCLUSIVEMAX: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveMax())); + } else { + return emptyStyledString; + } + default: + return new StyledString("error"); + } + } + + /** + * {@inheritDoc} + */ + public String getReadableString(Object object) { + if (object instanceof TimerData) { + TimerData data = (TimerData) object; + StringBuilder sb = new StringBuilder(); + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + for (Column column : Column.values()) { + sb.append(getStyledTextForColumn(data, methodIdent, column).toString()); + sb.append('\t'); + } + return sb.toString(); + } + throw new RuntimeException("Could not create the human readable string!"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getColumnValues(Object object) { + if (object instanceof TimerData) { + TimerData data = (TimerData) object; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + List values = new ArrayList(); + for (Column column : Column.values()) { + values.add(getStyledTextForColumn(data, methodIdent, column).toString()); + } + return values; + } + throw new RuntimeException("Could not create the column values!"); + } + + /** + * {@inheritDoc} + */ + @Override + public Object[] getObjectsToSearch(Object tableInput) { + return timerDataList.toArray(); + } + + /** + * {@inheritDoc} + */ + @Override + public SubViewClassification getSubViewClassification() { + return SubViewClassification.SLAVE; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/MultiInvocDataInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/MultiInvocDataInputController.java new file mode 100644 index 000000000..3c303f8ee --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/MultiInvocDataInputController.java @@ -0,0 +1,188 @@ +package info.novatec.inspectit.rcp.editor.table.input; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; +import info.novatec.inspectit.rcp.editor.table.TableViewerComparator; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; + +/** + * This view only displays the {@link info.novatec.inspectit.communication.data.TimerData} that are + * in the invocations provided by the invocationIdsList to this view via the + * {@link InputDefinition#getAdditionalOption(Object)}. + * + * @author Ivan Senic + * + */ +public class MultiInvocDataInputController extends InvocOverviewInputController { + + /** + * Key for multi invocation list in the additional options. + */ + public static final Object ADDITIONAL_OPTION_KEY = "MULTI_INVOCATION_LIST"; + + /** + * List of invocations to be loaded. + */ + private List invocationList; + + /** + * List of loaded invocations that are complete. + */ + private List loadedInvocations; + + /** + * List of invocations that are selected. + */ + private List selectedList; + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + if (inputDefinition.hasInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.COMBINED_INVOCATIONS_EXTRAS_MARKER)) { + invocationList = inputDefinition.getInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.COMBINED_INVOCATIONS_EXTRAS_MARKER).getTemplates(); + } + + super.setInputDefinition(inputDefinition); + } + + /** + * {@inheritDoc} + */ + @Override + public void doubleClick(DoubleClickEvent event) { + } + + /** + * {@inheritDoc} + */ + @Override + public Set getPreferenceIds() { + Set preferences = EnumSet.noneOf(PreferenceId.class); + if (getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) { + preferences.add(PreferenceId.CLEAR_BUFFER); + } + return preferences; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canOpenInput(List data) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public ViewerComparator getComparator() { + TableViewerComparator invocationDataViewerComparator = new TableViewerComparator(); + for (Column column : Column.values()) { + ResultComparator resultComparator = new ResultComparator(column.dataComparator, getCachedDataService()); + invocationDataViewerComparator.addColumn(getMappedTableViewerColumn(column).getColumn(), resultComparator); + } + + return invocationDataViewerComparator; + } + + /** + * {@inheritDoc} + */ + @Override + public void doRefresh(IProgressMonitor monitor, IRootEditor rootEditor) { + monitor.beginTask("Updating Invocation Overview", invocationList.size()); + monitor.subTask("Retrieving the Invocation Overview from the CMR"); + loadedInvocations = new ArrayList(); + for (InvocationSequenceData template : invocationList) { + loadedInvocations.add(getDataAccessService().getInvocationSequenceDetail(template)); + monitor.worked(1); + } + getInvocationSequenceData().clear(); + if (!loadedInvocations.isEmpty()) { + monitor.subTask("Displaying the Invocation Overview"); + getInvocationSequenceData().addAll(loadedInvocations); + } + selectedList = new ArrayList(loadedInvocations); + passSelectedList(); + monitor.done(); + } + + /** + * {@inheritDoc} + */ + @Override + public void objectChecked(Object object, boolean checked) { + if (object instanceof InvocationSequenceData) { + InvocationSequenceData selected = (InvocationSequenceData) object; + if (checked) { + for (InvocationSequenceData inData : loadedInvocations) { + if (inData.getId() == selected.getId()) { + if (!selectedList.contains(inData)) { + selectedList.add(inData); + } + break; + } + } + } else { + for (InvocationSequenceData inData : selectedList) { + if (inData.getId() == selected.getId()) { + selectedList.remove(inData); + break; + } + } + } + passSelectedList(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isCheckStyle() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean areItemsInitiallyChecked() { + return true; + } + + /** + * Passes the list of selected invocations to the subviews. + */ + private void passSelectedList() { + Display.getDefault().asyncExec(new Runnable() { + public void run() { + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + IWorkbenchPage page = window.getActivePage(); + IRootEditor rootEditor = (IRootEditor) page.getActiveEditor(); + rootEditor.setDataInput(selectedList); + } + }); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/NavigationInvocOverviewInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/NavigationInvocOverviewInputController.java new file mode 100644 index 000000000..4575aba1e --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/NavigationInvocOverviewInputController.java @@ -0,0 +1,103 @@ +package info.novatec.inspectit.rcp.editor.table.input; + +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.communication.data.InvocationAwareData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; +import info.novatec.inspectit.rcp.editor.table.TableViewerComparator; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.viewers.ViewerComparator; + +/** + * A extension of the {@link InvocOverviewInputController} that displays the invocations that are + * statically transfered to the view via invocation aware data. + * + * @author Ivan Senic + * + */ +public class NavigationInvocOverviewInputController extends InvocOverviewInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.table.navigationinvocoverview"; + + /** + * List of all invocation sequences that can be displayed. + */ + private List invocationAwareDataList; + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + if (inputDefinition.hasInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.NAVIGATION_STEPPING_EXTRAS_MARKER)) { + invocationAwareDataList = inputDefinition.getInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.NAVIGATION_STEPPING_EXTRAS_MARKER).getInvocationAwareDataList(); + } + + super.setInputDefinition(inputDefinition); + } + + /** + * {@inheritDoc} + */ + @Override + public Set getPreferenceIds() { + Set preferences = EnumSet.noneOf(PreferenceId.class); + if (getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) { + preferences.add(PreferenceId.CLEAR_BUFFER); + } + preferences.add(PreferenceId.ITEMCOUNT); + return preferences; + } + + /** + * {@inheritDoc} + */ + @Override + public ViewerComparator getComparator() { + TableViewerComparator invocationDataViewerComparator = new TableViewerComparator(); + for (Column column : Column.values()) { + ResultComparator resultComparator = new ResultComparator(column.dataComparator, getCachedDataService()); + invocationDataViewerComparator.addColumn(getMappedTableViewerColumn(column).getColumn(), resultComparator); + } + + return invocationDataViewerComparator; + } + + /** + * {@inheritDoc} + */ + @Override + public void doRefresh(IProgressMonitor monitor, IRootEditor rootEditor) { + monitor.beginTask("Updating Invocation Overview", IProgressMonitor.UNKNOWN); + monitor.subTask("Retrieving the Invocation Overview from the CMR"); + List invocData; + Set invocationIdsSet = new HashSet(); + for (InvocationAwareData invocationAwareData : invocationAwareDataList) { + if (null != invocationAwareData.getInvocationParentsIdSet()) { + invocationIdsSet.addAll(invocationAwareData.getInvocationParentsIdSet()); + } + } + long platformIdent = getInputDefinition().getIdDefinition().getPlatformId(); + invocData = getDataAccessService().getInvocationSequenceOverview(platformIdent, invocationIdsSet, getLimit(), getResultComparator()); + getInvocationSequenceData().clear(); + if (!invocData.isEmpty()) { + monitor.subTask("Displaying the Invocation Overview"); + getInvocationSequenceData().addAll(invocData); + } + monitor.done(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/SqlParameterAggregationInputControler.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/SqlParameterAggregationInputControler.java new file mode 100644 index 000000000..adc2ae1cd --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/SqlParameterAggregationInputControler.java @@ -0,0 +1,436 @@ +package info.novatec.inspectit.rcp.editor.table.input; + +import info.novatec.inspectit.cmr.service.ICachedDataService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.comparator.IDataComparator; +import info.novatec.inspectit.communication.comparator.InvocationAwareDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.communication.comparator.SqlStatementDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.TimerDataComparatorEnum; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; +import info.novatec.inspectit.rcp.editor.table.TableViewerComparator; +import info.novatec.inspectit.rcp.editor.text.input.SqlStatementTextInputController.SqlHolderHelper; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; +import info.novatec.inspectit.rcp.preferences.PreferencesUtils; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; + +/** + * Input controller for the table view that is displayed below the aggregated SQL table. + *

+ * Shows one statement aggregated on parameter basis. + * + * @author Ivan Senic + * + */ +public class SqlParameterAggregationInputControler extends AbstractTableInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.table.sqlparameteraggregation"; + + /** + * @author Ivan Senic + * + */ + private static enum Column { + /** The Parameters column. */ + PARAMETERS("Parameters", 600, null, SqlStatementDataComparatorEnum.PARAMETERS), + /** Invocation Affiliation. */ + INVOCATION_AFFILLIATION("In Invocations", 120, InspectITImages.IMG_INVOCATION, InvocationAwareDataComparatorEnum.INVOCATION_AFFILIATION), + /** The count column. */ + COUNT("Count", 80, null, TimerDataComparatorEnum.COUNT), + /** The average column. */ + AVERAGE("Avg (ms)", 80, null, TimerDataComparatorEnum.AVERAGE), + /** The min column. */ + MIN("Min (ms)", 80, null, TimerDataComparatorEnum.MIN), + /** The max column. */ + MAX("Max (ms)", 80, null, TimerDataComparatorEnum.MAX), + /** The duration column. */ + DURATION("Duration (ms)", 80, null, TimerDataComparatorEnum.DURATION); + + /** The name. */ + private String name; + /** The width of the column. */ + private int width; + /** The image descriptor. Can be null */ + private Image image; + /** Comparator for the column. */ + private IDataComparator dataComparator; + + /** + * Default constructor which creates a column enumeration object. + * + * @param name + * The name of the column. + * @param width + * The width of the column. + * @param imageName + * The name of the image. Names are defined in {@link InspectITImages}. + * @param dataComparator + * Comparator for the column. + */ + private Column(String name, int width, String imageName, IDataComparator dataComparator) { + this.name = name; + this.width = width; + this.image = InspectIT.getDefault().getImage(imageName); + this.dataComparator = dataComparator; + } + + /** + * Converts an ordinal into a column. + * + * @param i + * The ordinal. + * @return The appropriate column. + */ + public static Column fromOrd(int i) { + if (i < 0 || i >= Column.values().length) { + throw new IndexOutOfBoundsException("Invalid ordinal"); + } + return Column.values()[i]; + } + + } + + /** + * Decimal places. + */ + private int timeDecimalPlaces = PreferencesUtils.getIntValue(PreferencesConstants.DECIMAL_PLACES); + + /** + * {@inheritDoc} + */ + public void createColumns(TableViewer tableViewer) { + for (Column column : Column.values()) { + TableViewerColumn viewerColumn = new TableViewerColumn(tableViewer, SWT.NONE); + viewerColumn.getColumn().setMoveable(true); + viewerColumn.getColumn().setResizable(true); + viewerColumn.getColumn().setText(column.name); + viewerColumn.getColumn().setWidth(column.width); + if (null != column.image) { + viewerColumn.getColumn().setImage(column.image); + } + mapTableViewerColumn(column, viewerColumn); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Set getPreferenceIds() { + Set preferences = EnumSet.noneOf(PreferenceId.class); + if (getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) { + preferences.add(PreferenceId.CLEAR_BUFFER); + } + preferences.add(PreferenceId.TIME_RESOLUTION); + return preferences; + } + + /** + * {@inheritDoc} + */ + @Override + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + switch (preferenceEvent.getPreferenceId()) { + case TIME_RESOLUTION: + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeResolution.TIME_DECIMAL_PLACES_ID)) { + timeDecimalPlaces = (Integer) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeResolution.TIME_DECIMAL_PLACES_ID); + } + break; + default: + break; + } + } + + /** + * {@inheritDoc} + */ + @Override + public Object getTableInput() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canOpenInput(List data) { + if (data != null) { + for (DefaultData defaultData : data) { + if (!(defaultData instanceof SqlHolderHelper)) { + return false; + } else if (!((SqlHolderHelper) defaultData).isMaster()) { + return false; + } + } + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public IContentProvider getContentProvider() { + return new SqlParameterContentProvider(); + } + + /** + * {@inheritDoc} + */ + @Override + public IBaseLabelProvider getLabelProvider() { + return new SqlLabelProvider(); + } + + /** + * {@inheritDoc} + */ + @Override + public ViewerComparator getComparator() { + ICachedDataService cachedDataService = getInputDefinition().getRepositoryDefinition().getCachedDataService(); + TableViewerComparator sqlViewerComparator = new TableViewerComparator(); + for (Column column : Column.values()) { + ResultComparator resultComparator = new ResultComparator(column.dataComparator, cachedDataService); + sqlViewerComparator.addColumn(getMappedTableViewerColumn(column).getColumn(), resultComparator); + } + + return sqlViewerComparator; + } + + /** + * {@inheritDoc} + */ + @Override + public String getReadableString(Object object) { + if (object instanceof SqlStatementData) { + SqlStatementData data = (SqlStatementData) object; + StringBuilder sb = new StringBuilder(); + for (Column column : Column.values()) { + sb.append(getStyledTextForColumn(data, column).toString()); + sb.append('\t'); + } + return sb.toString(); + } + throw new RuntimeException("Could not create the human readable string!"); + } + + /** + * {@inheritDoc} + *

+ * We don't search the input directly. + */ + @SuppressWarnings("unchecked") + @Override + public Object[] getObjectsToSearch(Object tableInput) { + if (null != tableInput) { + List sqlStatementDatas = new ArrayList<>(); + // we can cast here safely cause only this can be input + List helpers = (List) tableInput; + for (SqlHolderHelper helper : helpers) { + sqlStatementDatas.addAll(helper.getSqlStatementDataList()); + } + return sqlStatementDatas.toArray(new SqlStatementData[sqlStatementDatas.size()]); + } + return new Object[0]; + } + + /** + * {@inheritDoc} + */ + @Override + public List getColumnValues(Object object) { + if (object instanceof SqlStatementData) { + SqlStatementData data = (SqlStatementData) object; + List values = new ArrayList(); + for (Column column : Column.values()) { + values.add(getStyledTextForColumn(data, column).toString()); + } + return values; + } + throw new RuntimeException("Could not create the column values!"); + } + + /** + * {@inheritDoc} + */ + @Override + public void doubleClick(DoubleClickEvent event) { + final StructuredSelection selection = (StructuredSelection) event.getSelection(); + if (!selection.isEmpty()) { + Object selected = selection.getFirstElement(); + if (selected instanceof SqlStatementData) { + List sqlList = new ArrayList(); + sqlList.add((SqlStatementData) selected); + passSqlWithParameters(sqlList); + } + } + } + + /** + * Returns styled string for the column. + * + * @param data + * Data to return string for. + * @param enumId + * Enumerated column. + * @return {@link StyledString}. + */ + private StyledString getStyledTextForColumn(SqlStatementData data, Column enumId) { + switch (enumId) { + case PARAMETERS: + return new StyledString(TextFormatter.getSqlParametersText(data.getParameterValues())); + case INVOCATION_AFFILLIATION: + int percentage = (int) (data.getInvocationAffiliationPercentage() * 100); + int invocations = 0; + if (null != data.getInvocationParentsIdSet()) { + invocations = data.getInvocationParentsIdSet().size(); + } + return TextFormatter.getInvocationAffilliationPercentageString(percentage, invocations); + case COUNT: + return new StyledString(Long.toString(data.getCount())); + case AVERAGE: + return new StyledString(NumberFormatter.formatDouble(data.getAverage(), timeDecimalPlaces)); + case MIN: + return new StyledString(NumberFormatter.formatDouble(data.getMin(), timeDecimalPlaces)); + case MAX: + return new StyledString(NumberFormatter.formatDouble(data.getMax(), timeDecimalPlaces)); + case DURATION: + return new StyledString(NumberFormatter.formatDouble(data.getDuration(), timeDecimalPlaces)); + default: + return new StyledString("error"); + } + } + + /** + * Passes the {@link SqlStatementData} to the bottom view that displays the SQL string. + * + * @param sqlStatementDataList + * List to pass. + */ + private void passSqlWithParameters(final List sqlStatementDataList) { + final SqlHolderHelper sqlHolderHelper = new SqlHolderHelper(sqlStatementDataList, false); + Display.getDefault().asyncExec(new Runnable() { + public void run() { + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + IWorkbenchPage page = window.getActivePage(); + IRootEditor rootEditor = (IRootEditor) page.getActiveEditor(); + if (null != rootEditor) { + rootEditor.setDataInput(Collections.singletonList(sqlHolderHelper)); + } + } + }); + } + + /** + * The sql label provider used by this view. + * + * @author Ivan Senic + * + */ + private final class SqlLabelProvider extends StyledCellIndexLabelProvider { + + /** + * Creates the styled text. + * + * @param element + * The element to create the styled text for. + * @param index + * The index in the column. + * @return The created styled string. + */ + @Override + public StyledString getStyledText(Object element, int index) { + SqlStatementData data = (SqlStatementData) element; + Column enumId = Column.fromOrd(index); + + return getStyledTextForColumn(data, enumId); + } + + } + + /** + * @author Ivan Senic + * + */ + private final class SqlParameterContentProvider implements IStructuredContentProvider { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public void inputChanged(final Viewer viewer, Object oldInput, Object newInput) { + if (null == newInput || Objects.equals(newInput, Collections.emptyList())) { + viewer.getControl().setEnabled(false); + } else { + List helperList = (List) newInput; + final List list = helperList.get(0).getSqlStatementDataList(); + if (null == list || Objects.equals(list, Collections.emptyList())) { + viewer.getControl().setEnabled(false); + } else { + viewer.getControl().setEnabled(true); + SqlParameterAggregationInputControler.this.passSqlWithParameters(list); + StructuredSelection structuredSelection = new StructuredSelection(list.get(0)); + viewer.setSelection(structuredSelection, true); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + } + + /** + * {@inheritDoc} + */ + @Override + public Object[] getElements(Object inputElement) { + if (inputElement instanceof List && !((List) inputElement).isEmpty()) { + Object first = ((List) inputElement).get(0); + if (first instanceof SqlHolderHelper) { + return ((SqlHolderHelper) first).getSqlStatementDataList().toArray(); + } + } + return new Object[0]; + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/TableInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/TableInputController.java new file mode 100644 index 000000000..932df521c --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/TableInputController.java @@ -0,0 +1,203 @@ +package info.novatec.inspectit.rcp.editor.table.input; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; +import info.novatec.inspectit.rcp.editor.root.SubViewClassificationController; + +import java.util.List; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.widgets.TableColumn; + +/** + * The interface for all table input controller. + * + * @author Patrice Bouillet + * + */ +public interface TableInputController extends SubViewClassificationController { + + /** + * Sets the input definition of this controller. + * + * @param inputDefinition + * The input definition. + */ + void setInputDefinition(InputDefinition inputDefinition); + + /** + * Creates the columns in the given table viewer. + * + * @param tableViewer + * The table viewer. + */ + void createColumns(TableViewer tableViewer); + + /** + * The {@link info.novatec.inspectit.rcp.editor.table.TableSubView} might need to alter the + * column width/visibility if the column has the remembered size. With this method the + * controller gives or denies the {@link info.novatec.inspectit.rcp.editor.table.TableSubView} + * to alter the column width. + * + * @param tableColumn + * {@link TableColumn} + * + * @return Returns true if the {@link TableColumn} can be altered. + */ + boolean canAlterColumnWidth(TableColumn tableColumn); + + /** + * Generates and returns the input for the table. Returning null is possible and + * indicates most of the time that there is no default list or object to display in the table. + * For some {@link DefaultData} objects, the method {@link #canOpenInput(List)} should return + * true so that the input object is set by the + * {@link info.novatec.inspectit.rcp.editor.table.TableSubView}. + * + * @return The table input or null if nothing to display for default. + */ + Object getTableInput(); + + /** + * Returns the content provider for the {@link TableViewer}. + * + * @return The content provider. + * @see IContentProvider + */ + IContentProvider getContentProvider(); + + /** + * Returns the label provider for the {@link TableViewer}. + * + * @return The label provider + * @see IBaseLabelProvider + */ + IBaseLabelProvider getLabelProvider(); + + /** + * Returns the comparator for the {@link TableViewer}. Can be null to indicate that + * no sorting of the elements should be done. + * + * @return The table viewer comparator. + */ + ViewerComparator getComparator(); + + /** + * Sets the limit of the displayed elements in the table. + * + * @param limit + * The limit value. + */ + void setLimit(int limit); + + /** + * Refreshes the current data and updates the table input if new items are available. + * + * @param monitor + * The progress monitor. + * @param rootEditor + * RootEditor of the view that is being refreshed. + */ + void doRefresh(IProgressMonitor monitor, IRootEditor rootEditor); + + /** + * This method will be called when a double click event is executed. + * + * @param event + * The event object. + */ + void doubleClick(DoubleClickEvent event); + + /** + * Returns true if the controller can open the input which consists of one or + * several {@link DefaultData} objects. + * + * @param data + * The data which is checked if the controller can open it. + * @return Returns true if the controller can open the input. + */ + boolean canOpenInput(List data); + + /** + * Returns all needed preference IDs. + * + * @return A {@link Set} containing all {@link PreferenceId}. Returning null is not + * permitted here. At least a {@link java.util.Collections#EMPTY_SET} should be + * returned. + */ + Set getPreferenceIds(); + + /** + * This method is called whenever something is changed in one of the preferences. + * + * @param preferenceEvent + * The event object containing the changed objects. + */ + void preferenceEventFired(PreferenceEvent preferenceEvent); + + /** + * This method creates a human readable string out of the given object (which is object from the + * table model). + * + * @param object + * The object to create the string from. + * @return The created human readable string. + */ + String getReadableString(Object object); + + /** + * Return the values of all columns in the table for the given object. Not visible columns + * values will also be included. The order of the values will be same to the initial table + * column order, thus not reflecting the current state of the table if the columns were moved. + * + * @param object + * Object to get values for. + * @return List of string representing the values. + */ + List getColumnValues(Object object); + + /** + * Returns the list of the objects that should be searched. + * + * @param tableInput + * Current input of the table. The {@link TableInputController} is responsible to + * modify the input if necessary. + * @return Returns the list of the objects that should be searched. + */ + Object[] getObjectsToSearch(Object tableInput); + + /** + * Disposes the table input. + */ + void dispose(); + + /** + * Signals that the object has been check so that input controller can perform necessary + * actions. + * + * @param object + * Object that has be checked. + * @param checked + * True if object is checked, false if it is not. + */ + void objectChecked(Object object, boolean checked); + + /** + * @return If the table should apply the SWT.CHECK style. + */ + boolean isCheckStyle(); + + /** + * @return The initial state of check box for all elements. + */ + boolean areItemsInitiallyChecked(); + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/TaggedHttpTimerDataInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/TaggedHttpTimerDataInputController.java new file mode 100644 index 000000000..871ff3754 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/TaggedHttpTimerDataInputController.java @@ -0,0 +1,401 @@ +package info.novatec.inspectit.rcp.editor.table.input; + +import info.novatec.inspectit.cmr.service.ICachedDataService; +import info.novatec.inspectit.communication.comparator.HttpTimerDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.IDataComparator; +import info.novatec.inspectit.communication.comparator.InvocationAwareDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.communication.comparator.TimerDataComparatorEnum; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; +import info.novatec.inspectit.rcp.editor.table.TableViewerComparator; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; + +/** + * Input controller for the tagged http view. + * + * @author Stefan Siegl + */ +public class TaggedHttpTimerDataInputController extends AbstractHttpInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.table.taggedhttptimerdata"; + + /** + * The private inner enumeration used to define the used IDs which are mapped into the columns. + * The order in this enumeration represents the order of the columns. If it is reordered, + * nothing else has to be changed. + * + * @author Stefan Siegl + * + */ + private static enum Column { + /** The time column. */ + CHARTING("Charting", 20, null, TimerDataComparatorEnum.CHARTING), + /** The package column. */ + TAG_VALUE("Tag Value", 300, InspectITImages.IMG_HTTP_TAGGED, HttpTimerDataComparatorEnum.TAG_VALUE), + /** The request method. */ + HTTP_METHOD("Method", 80, null, HttpTimerDataComparatorEnum.HTTP_METHOD), + /** Invocation Affiliation. */ + INVOCATION_AFFILLIATION("In Invocations", 120, InspectITImages.IMG_INVOCATION, InvocationAwareDataComparatorEnum.INVOCATION_AFFILIATION), + /** The count column. */ + COUNT("Count", 60, null, TimerDataComparatorEnum.COUNT), + /** The average column. */ + AVERAGE("Avg (ms)", 60, null, TimerDataComparatorEnum.AVERAGE), + /** The minimum column. */ + MIN("Min (ms)", 60, null, TimerDataComparatorEnum.MIN), + /** The maximum column. */ + MAX("Max (ms)", 60, null, TimerDataComparatorEnum.MAX), + /** The duration column. */ + DURATION("Duration (ms)", 70, null, TimerDataComparatorEnum.DURATION), + /** The average exclusive duration column. */ + EXCLUSIVEAVERAGE("Exc. Avg (ms)", 80, null, TimerDataComparatorEnum.EXCLUSIVEAVERAGE), + /** The min exclusive duration column. */ + EXCLUSIVEMIN("Exc. Min (ms)", 80, null, TimerDataComparatorEnum.EXCLUSIVEMIN), + /** The max exclusive duration column. */ + EXCLUSIVEMAX("Exc. Max (ms)", 80, null, TimerDataComparatorEnum.EXCLUSIVEMAX), + /** The total exclusive duration column. */ + EXCLUSIVESUM("Exc. duration (ms)", 80, null, TimerDataComparatorEnum.EXCLUSIVEDURATION), + /** The cpu average column. */ + CPUAVERAGE("Cpu Avg (ms)", 60, null, TimerDataComparatorEnum.CPUAVERAGE), + /** The cpu minimum column. */ + CPUMIN("Cpu Min (ms)", 60, null, TimerDataComparatorEnum.CPUMIN), + /** The cpu maximum column. */ + CPUMAX("Cpu Max (ms)", 60, null, TimerDataComparatorEnum.CPUMAX), + /** The cpu duration column. */ + CPUDURATION("Cpu Duration (ms)", 70, null, TimerDataComparatorEnum.CPUDURATION); + + /** The name. */ + private String name; + /** The width of the column. */ + private int width; + /** The image descriptor. Can be null */ + private Image image; + /** Comparator for the column. */ + private IDataComparator dataComparator; + + /** + * Default constructor which creates a column enumeration object. + * + * @param name + * The name of the column. + * @param width + * The width of the column. + * @param imageName + * The name of the image. Names are defined in {@link InspectITImages}. + * @param dataComparator + * Comparator for the column. + */ + private Column(String name, int width, String imageName, IDataComparator dataComparator) { + this.name = name; + this.width = width; + this.image = InspectIT.getDefault().getImage(imageName); + this.dataComparator = dataComparator; + } + + /** + * Converts an ordinal into a column. + * + * @param i + * The ordinal. + * @return The appropriate column. + */ + public static Column fromOrd(int i) { + if (i < 0 || i >= Column.values().length) { + throw new IndexOutOfBoundsException("Invalid ordinal"); + } + return Column.values()[i]; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void doRefresh(IProgressMonitor monitor, IRootEditor rootEditor) { + monitor.beginTask("Getting HTTP data information", IProgressMonitor.UNKNOWN); + List aggregatedTimerData; + + if (autoUpdate) { + aggregatedTimerData = httptimerDataAccessService.getTaggedAggregatedTimerData(template, httpCatorizationOnRequestMethodActive); + } else { + aggregatedTimerData = httptimerDataAccessService.getTaggedAggregatedTimerData(template, httpCatorizationOnRequestMethodActive, fromDate, toDate); + } + + timerDataList.clear(); + if (CollectionUtils.isNotEmpty(aggregatedTimerData)) { + timerDataList.addAll(aggregatedTimerData); + } + + monitor.done(); + } + + @Override + public void createColumns(TableViewer tableViewer) { + for (Column column : Column.values()) { + TableViewerColumn viewerColumn = new TableViewerColumn(tableViewer, SWT.NONE); + viewerColumn.getColumn().setMoveable(true); + viewerColumn.getColumn().setResizable(true); + viewerColumn.getColumn().setText(column.name); + viewerColumn.getColumn().setWidth(column.width); + if (null != column.image) { + viewerColumn.getColumn().setImage(column.image); + } + mapTableViewerColumn(column, viewerColumn); + } + } + + /** + * {@inheritDoc} + */ + @Override + public IBaseLabelProvider getLabelProvider() { + return new StyledCellIndexLabelProvider() { + /** + * {@inheritDoc} + */ + @Override + public StyledString getStyledText(Object element, int index) { + HttpTimerData data = (HttpTimerData) element; + Column enumId = Column.fromOrd(index); + + StyledString styledString = getStyledTextForColumn(data, enumId); + if (addWarnSign(data, enumId)) { + styledString.append(TextFormatter.getWarningSign()); + } + return styledString; + } + + /** + * Decides if the warn sign should be added for the specific column. + * + * @param data + * TimerData + * @param column + * Column to check. + * @return True if warn sign should be added. + */ + private boolean addWarnSign(TimerData data, Column column) { + switch (column) { + case EXCLUSIVEAVERAGE: + case EXCLUSIVEMAX: + case EXCLUSIVEMIN: + case EXCLUSIVESUM: + int affPercentage = (int) (data.getInvocationAffiliationPercentage() * 100); + return data.isExclusiveTimeDataAvailable() && affPercentage < 100; + default: + return false; + } + } + + /** + * + * {@inheritDoc} + */ + @Override + protected Image getColumnImage(Object element, int index) { + HttpTimerData data = (HttpTimerData) element; + Column enumId = Column.fromOrd(index); + + switch (enumId) { + case CHARTING: + if (data.isCharting()) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_CHART_PIE); + } + default: + return super.getColumnImage(element, index); + } + + } + + /** + * {@inheritDoc} + */ + @Override + public String getToolTipText(Object element, int index) { + HttpTimerData data = (HttpTimerData) element; + Column enumId = Column.fromOrd(index); + + switch (enumId) { + case CHARTING: + if (data.isCharting()) { + return "Duration chart can be displayed for this HTTP data."; + } + default: + return super.getToolTipText(element, index); + } + } + }; + } + + /** + * {@inheritDoc} + */ + @Override + public ViewerComparator getComparator() { + ICachedDataService cachedDataService = getInputDefinition().getRepositoryDefinition().getCachedDataService(); + TableViewerComparator httpTimerDataViewerComparator = new TableViewerComparator(); + for (Column column : Column.values()) { + ResultComparator resultComparator = new ResultComparator(column.dataComparator, cachedDataService); + httpTimerDataViewerComparator.addColumn(getMappedTableViewerColumn(column).getColumn(), resultComparator); + } + + return httpTimerDataViewerComparator; + } + + /** + * {@inheritDoc} + */ + @Override + public String getReadableString(Object object) { + if (object instanceof HttpTimerData) { + HttpTimerData data = (HttpTimerData) object; + StringBuilder sb = new StringBuilder(); + for (Column column : Column.values()) { + sb.append(getStyledTextForColumn(data, column).toString()); + sb.append('\t'); + } + return sb.toString(); + } + throw new RuntimeException("Could not create the human readable string!"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getColumnValues(Object object) { + if (object instanceof HttpTimerData) { + HttpTimerData data = (HttpTimerData) object; + List values = new ArrayList(); + for (Column column : Column.values()) { + values.add(getStyledTextForColumn(data, column).toString()); + } + return values; + } + throw new RuntimeException("Could not create the column values!"); + } + + /** + * Returns the styled text for a specific column. + * + * @param data + * The data object to extract the information from. + * @param enumId + * The enumeration ID. + * @return The styled string containing the information from the data object. + */ + private StyledString getStyledTextForColumn(HttpTimerData data, Column enumId) { + switch (enumId) { + case CHARTING: + return emptyStyledString; + case TAG_VALUE: + return new StyledString(data.getInspectItTaggingHeaderValue()); + case HTTP_METHOD: + return new StyledString(data.getRequestMethod()); + case INVOCATION_AFFILLIATION: + int percentage = (int) (data.getInvocationAffiliationPercentage() * 100); + int invocations = 0; + if (null != data.getInvocationParentsIdSet()) { + invocations = data.getInvocationParentsIdSet().size(); + } + return TextFormatter.getInvocationAffilliationPercentageString(percentage, invocations); + case COUNT: + return new StyledString(String.valueOf(data.getCount())); + case AVERAGE: + if (data.isTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getAverage(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case MIN: + if (data.isTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getMin(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case MAX: + if (data.isTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getMax(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case DURATION: + if (data.isTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getDuration(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case CPUAVERAGE: + if (data.isCpuMetricDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getCpuAverage(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case CPUMIN: + if (data.isCpuMetricDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getCpuMin(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case CPUMAX: + if (data.isCpuMetricDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getCpuMax(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case CPUDURATION: + if (data.isCpuMetricDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getCpuDuration(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case EXCLUSIVEAVERAGE: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveAverage(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case EXCLUSIVEMAX: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveMax(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case EXCLUSIVEMIN: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveMin(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case EXCLUSIVESUM: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveDuration(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + default: + return new StyledString("error"); + } + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/TimerDataInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/TimerDataInputController.java new file mode 100644 index 000000000..005a12bdf --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/TimerDataInputController.java @@ -0,0 +1,631 @@ +package info.novatec.inspectit.rcp.editor.table.input; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.service.ICachedDataService; +import info.novatec.inspectit.cmr.service.ITimerDataAccessService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.comparator.IDataComparator; +import info.novatec.inspectit.communication.comparator.InvocationAwareDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.MethodSensorDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.communication.comparator.TimerDataComparatorEnum; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId.LiveMode; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; +import info.novatec.inspectit.rcp.editor.table.TableViewerComparator; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; +import info.novatec.inspectit.rcp.preferences.PreferencesUtils; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.util.ArrayList; +import java.util.Date; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; + +/** + * Table input controller for the aggregated Timer data view. + * + * @author Ivan Senic + * + */ +public class TimerDataInputController extends AbstractTableInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.table.aggregatedtimerdata"; + + /** + * The private inner enumeration used to define the used IDs which are mapped into the columns. + * The order in this enumeration represents the order of the columns. If it is reordered, + * nothing else has to be changed. + * + * @author Patrice Bouillet + * @author Ivan Senic + * + */ + private static enum Column { + /** The time column. */ + CHARTING("Charting", 20, null, TimerDataComparatorEnum.CHARTING), + /** The package column. */ + PACKAGE("Package", 200, InspectITImages.IMG_PACKAGE, MethodSensorDataComparatorEnum.PACKAGE), + /** The class column. */ + CLASS("Class", 200, InspectITImages.IMG_CLASS, MethodSensorDataComparatorEnum.CLASS), + /** The method column. */ + METHOD("Method", 300, InspectITImages.IMG_METHOD, MethodSensorDataComparatorEnum.METHOD), + /** Invocation Affiliation. */ + INVOCATION_AFFILLIATION("In Invocations", 120, InspectITImages.IMG_INVOCATION, InvocationAwareDataComparatorEnum.INVOCATION_AFFILIATION), + /** The count column. */ + COUNT("Count", 60, null, TimerDataComparatorEnum.COUNT), + /** The average column. */ + AVERAGE("Avg (ms)", 60, null, TimerDataComparatorEnum.AVERAGE), + /** The minimum column. */ + MIN("Min (ms)", 60, null, TimerDataComparatorEnum.MIN), + /** The maximum column. */ + MAX("Max (ms)", 60, null, TimerDataComparatorEnum.MAX), + /** The duration column. */ + DURATION("Duration (ms)", 70, null, TimerDataComparatorEnum.DURATION), + /** The average exclusive duration column. */ + EXCLUSIVEAVERAGE("Exc. Avg (ms)", 80, null, TimerDataComparatorEnum.EXCLUSIVEAVERAGE), + /** The min exclusive duration column. */ + EXCLUSIVEMIN("Exc. Min (ms)", 80, null, TimerDataComparatorEnum.EXCLUSIVEMIN), + /** The max exclusive duration column. */ + EXCLUSIVEMAX("Exc. Max (ms)", 80, null, TimerDataComparatorEnum.EXCLUSIVEMAX), + /** The total exclusive duration column. */ + EXCLUSIVESUM("Exc. duration (ms)", 80, null, TimerDataComparatorEnum.EXCLUSIVEDURATION), + /** The cpu average column. */ + CPUAVERAGE("Cpu Avg (ms)", 60, null, TimerDataComparatorEnum.CPUAVERAGE), + /** The cpu minimum column. */ + CPUMIN("Cpu Min (ms)", 60, null, TimerDataComparatorEnum.CPUMIN), + /** The cpu maximum column. */ + CPUMAX("Cpu Max (ms)", 60, null, TimerDataComparatorEnum.CPUMAX), + /** The cpu duration column. */ + CPUDURATION("Cpu Duration (ms)", 70, null, TimerDataComparatorEnum.CPUDURATION); + + /** The name. */ + private String name; + /** The width of the column. */ + private int width; + /** The image descriptor. Can be null */ + private Image image; + /** Comparator for the column. */ + private IDataComparator dataComparator; + + /** + * Default constructor which creates a column enumeration object. + * + * @param name + * The name of the column. + * @param width + * The width of the column. + * @param imageName + * The name of the image. Names are defined in {@link InspectITImages}. + * @param dataComparator + * Comparator for the column. + */ + private Column(String name, int width, String imageName, IDataComparator dataComparator) { + this.name = name; + this.width = width; + this.image = InspectIT.getDefault().getImage(imageName); + this.dataComparator = dataComparator; + } + + /** + * Converts an ordinal into a column. + * + * @param i + * The ordinal. + * @return The appropriate column. + */ + public static Column fromOrd(int i) { + if (i < 0 || i >= Column.values().length) { + throw new IndexOutOfBoundsException("Invalid ordinal"); + } + return Column.values()[i]; + } + + } + + /** + * Timer data access service. + */ + private ITimerDataAccessService timerDataAccessService; + + /** + * Global data access service. + */ + private ICachedDataService cachedDataService; + + /** + * List of Timer data to be displayed. + */ + private List timerDataList = new ArrayList(); + + /** + * Template object used for querying. + */ + private TimerData template; + + /** + * Empty styled string. + */ + private final StyledString emptyStyledString = new StyledString(); + + /** + * Date to display invocations from. + */ + private Date fromDate = null; + + /** + * Date to display invocations to. + */ + private Date toDate = null; + + /** + * Are we in live mode. + */ + private boolean autoUpdate = LiveMode.ACTIVE_DEFAULT; + + /** + * Decimal places. + */ + private int timeDecimalPlaces = PreferencesUtils.getIntValue(PreferencesConstants.DECIMAL_PLACES); + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + template = new TimerData(); + template.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); + template.setMethodIdent(inputDefinition.getIdDefinition().getMethodId()); + + timerDataAccessService = inputDefinition.getRepositoryDefinition().getTimerDataAccessService(); + cachedDataService = inputDefinition.getRepositoryDefinition().getCachedDataService(); + } + + /** + * {@inheritDoc} + */ + @Override + public void createColumns(TableViewer tableViewer) { + for (Column column : Column.values()) { + TableViewerColumn viewerColumn = new TableViewerColumn(tableViewer, SWT.NONE); + viewerColumn.getColumn().setMoveable(true); + viewerColumn.getColumn().setResizable(true); + viewerColumn.getColumn().setText(column.name); + viewerColumn.getColumn().setWidth(column.width); + if (Column.EXCLUSIVEAVERAGE.equals(column) || Column.EXCLUSIVESUM.equals(column) || Column.EXCLUSIVEMIN.equals(column) || Column.EXCLUSIVEMAX.equals(column)) { + // TODO: Remove this tooltip and add it to the cell as soon as the image bug is + // fixed in Eclipse. + viewerColumn.getColumn().setToolTipText( + "Exclusive times can only be calculated correctly if the timer is within an invocation sequence. " + + "A warning marker is provided if not all timers are run within an invocation sequence. Please be aware that " + + "avg, sum, min and max calculations are reflecting only the timers inside an invocation sequence."); + } + + if (null != column.image) { + viewerColumn.getColumn().setImage(column.image); + } + mapTableViewerColumn(column, viewerColumn); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Set getPreferenceIds() { + Set preferences = EnumSet.noneOf(PreferenceId.class); + if (getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) { + preferences.add(PreferenceId.CLEAR_BUFFER); + preferences.add(PreferenceId.LIVEMODE); + } + preferences.add(PreferenceId.UPDATE); + preferences.add(PreferenceId.TIME_RESOLUTION); + preferences.add(PreferenceId.TIMELINE); + return preferences; + } + + /** + * {@inheritDoc} + */ + @Override + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + switch (preferenceEvent.getPreferenceId()) { + case TIMELINE: + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeLine.FROM_DATE_ID)) { + fromDate = (Date) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeLine.FROM_DATE_ID); + } + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeLine.TO_DATE_ID)) { + toDate = (Date) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeLine.TO_DATE_ID); + } + break; + case LIVEMODE: + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.LiveMode.BUTTON_LIVE_ID)) { + autoUpdate = (Boolean) preferenceEvent.getPreferenceMap().get(PreferenceId.LiveMode.BUTTON_LIVE_ID); + } + break; + case TIME_RESOLUTION: + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeResolution.TIME_DECIMAL_PLACES_ID)) { + timeDecimalPlaces = (Integer) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeResolution.TIME_DECIMAL_PLACES_ID); + } + break; + default: + break; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canOpenInput(List data) { + if (null == data) { + return false; + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Object getTableInput() { + return timerDataList; + } + + /** + * {@inheritDoc} + */ + @Override + public void doRefresh(IProgressMonitor monitor, IRootEditor rootEditor) { + monitor.beginTask("Getting timer data information", IProgressMonitor.UNKNOWN); + List aggregatedTimerData; + if (autoUpdate) { + aggregatedTimerData = timerDataAccessService.getAggregatedTimerData(template); + } else { + aggregatedTimerData = timerDataAccessService.getAggregatedTimerData(template, fromDate, toDate); + } + + timerDataList.clear(); + if (CollectionUtils.isNotEmpty(aggregatedTimerData)) { + timerDataList.addAll(aggregatedTimerData); + } + + monitor.done(); + } + + /** + * {@inheritDoc} + */ + @Override + public IContentProvider getContentProvider() { + return new TimerDataContentProvider(); + } + + /** + * {@inheritDoc} + */ + @Override + public IBaseLabelProvider getLabelProvider() { + return new TimerDataLabelProvider(); + } + + /** + * {@inheritDoc} + */ + @Override + public ViewerComparator getComparator() { + TableViewerComparator timerDataViewerComparator = new TableViewerComparator(); + for (Column column : Column.values()) { + ResultComparator resultComparator = new ResultComparator(column.dataComparator, cachedDataService); + timerDataViewerComparator.addColumn(getMappedTableViewerColumn(column).getColumn(), resultComparator); + } + + return timerDataViewerComparator; + } + + /** + * {@inheritDoc} + */ + @Override + public String getReadableString(Object object) { + if (object instanceof TimerData) { + TimerData data = (TimerData) object; + StringBuilder sb = new StringBuilder(); + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + for (Column column : Column.values()) { + sb.append(getStyledTextForColumn(data, methodIdent, column).toString()); + sb.append('\t'); + } + return sb.toString(); + } + throw new RuntimeException("Could not create the human readable string!"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getColumnValues(Object object) { + if (object instanceof TimerData) { + TimerData data = (TimerData) object; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + List values = new ArrayList(); + for (Column column : Column.values()) { + values.add(getStyledTextForColumn(data, methodIdent, column).toString()); + } + return values; + } + throw new RuntimeException("Could not create the column values!"); + } + + /** + * Content provider for the view. + * + * @author Ivan Senic + * + */ + private static final class TimerDataContentProvider implements IStructuredContentProvider { + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + } + + /** + * {@inheritDoc} + */ + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public Object[] getElements(Object inputElement) { + return ((List) inputElement).toArray(); + } + + } + + /** + * Label provider for the view. + * + * @author Ivan Senic + * + */ + private final class TimerDataLabelProvider extends StyledCellIndexLabelProvider { + + /** + * {@inheritDoc} + */ + @Override + public StyledString getStyledText(Object element, int index) { + TimerData data = (TimerData) element; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + Column enumId = Column.fromOrd(index); + + StyledString styledString = getStyledTextForColumn(data, methodIdent, enumId); + if (addWarnSign(data, enumId)) { + styledString.append(TextFormatter.getWarningSign()); + } + return styledString; + } + + /** + * Decides if the warn sign should be added for the specific column. + * + * @param data + * TimerData + * @param column + * Column to check. + * @return True if warn sign should be added. + */ + private boolean addWarnSign(TimerData data, Column column) { + switch (column) { + case EXCLUSIVEAVERAGE: + case EXCLUSIVEMAX: + case EXCLUSIVEMIN: + case EXCLUSIVESUM: + int affPercentage = (int) (data.getInvocationAffiliationPercentage() * 100); + return data.isExclusiveTimeDataAvailable() && affPercentage < 100; + default: + return false; + } + } + + /** + * + * {@inheritDoc} + */ + @Override + protected Image getColumnImage(Object element, int index) { + TimerData data = (TimerData) element; + Column enumId = Column.fromOrd(index); + + switch (enumId) { + case CHARTING: + if (data.isCharting()) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_CHART_PIE); + } + default: + return super.getColumnImage(element, index); + } + + } + + /** + * {@inheritDoc} + */ + @Override + public String getToolTipText(Object element, int index) { + TimerData data = (TimerData) element; + Column enumId = Column.fromOrd(index); + + switch (enumId) { + case CHARTING: + if (data.isCharting()) { + return "Duration chart can be displayed for this timer data."; + } + default: + return super.getToolTipText(element, index); + } + } + } + + /** + * Returns the styled text for a specific column. + * + * @param data + * The data object to extract the information from. + * @param methodIdent + * The method ident object. + * @param enumId + * The enumeration ID. + * @return The styled string containing the information from the data object. + */ + private StyledString getStyledTextForColumn(TimerData data, MethodIdent methodIdent, Column enumId) { + switch (enumId) { + case CHARTING: + return emptyStyledString; + case PACKAGE: + if (methodIdent.getPackageName() != null && !methodIdent.getPackageName().equals("")) { + return new StyledString(methodIdent.getPackageName()); + } else { + return new StyledString("(default)"); + } + case CLASS: + return new StyledString(methodIdent.getClassName()); + case METHOD: + return new StyledString(TextFormatter.getMethodWithParameters(methodIdent)); + case INVOCATION_AFFILLIATION: + int percentage = (int) (data.getInvocationAffiliationPercentage() * 100); + int invocations = 0; + if (null != data.getInvocationParentsIdSet()) { + invocations = data.getInvocationParentsIdSet().size(); + } + return TextFormatter.getInvocationAffilliationPercentageString(percentage, invocations); + case COUNT: + return new StyledString(String.valueOf(data.getCount())); + case AVERAGE: + if (data.isTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getAverage(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case MIN: + if (data.isTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getMin(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case MAX: + if (data.isTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getMax(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case DURATION: + if (data.isTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getDuration(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case CPUAVERAGE: + if (data.isCpuMetricDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getCpuAverage(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case CPUMIN: + if (data.isCpuMetricDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getCpuMin(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case CPUMAX: + if (data.isCpuMetricDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getCpuMax(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case CPUDURATION: + if (data.isCpuMetricDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getCpuDuration(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case EXCLUSIVEAVERAGE: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveAverage(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case EXCLUSIVEMAX: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveMax(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case EXCLUSIVEMIN: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveMin(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + case EXCLUSIVESUM: + if (data.isExclusiveTimeDataAvailable()) { + return new StyledString(NumberFormatter.formatDouble(data.getExclusiveDuration(), timeDecimalPlaces)); + } else { + return emptyStyledString; + } + default: + return new StyledString("error"); + } + } + + /** + * @return the timerDataList + */ + public List getTimerDataList() { + return timerDataList; + } + + /** + * @return the timerDataAccessService + */ + public ITimerDataAccessService getTimerDataAccessService() { + return timerDataAccessService; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/UngroupedExceptionOverviewInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/UngroupedExceptionOverviewInputController.java new file mode 100644 index 000000000..71a1b12db --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/table/input/UngroupedExceptionOverviewInputController.java @@ -0,0 +1,508 @@ +package info.novatec.inspectit.rcp.editor.table.input; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.service.ICachedDataService; +import info.novatec.inspectit.cmr.service.IExceptionDataAccessService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.comparator.DefaultDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.IDataComparator; +import info.novatec.inspectit.communication.comparator.InvocationAwareDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.MethodSensorDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory; +import info.novatec.inspectit.rcp.editor.preferences.IPreferenceGroup; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; +import info.novatec.inspectit.rcp.editor.table.RemoteTableViewerComparator; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; +import info.novatec.inspectit.rcp.preferences.PreferencesUtils; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Date; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; + +/** + * + * @author Eduard Tudenhoefner + * + */ +public class UngroupedExceptionOverviewInputController extends AbstractTableInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.table.ungroupedexceptionoverview"; + + /** + * The private inner enumeration used to define the used IDs which are mapped into the columns. + * The order in this enumeration represents the order of the columns. If it is reordered, + * nothing else has to be changed. + * + * @author Eduard Tudenhoefner + * + */ + private static enum Column { + /** The count column. */ + TIMESTAMP("Timestamp", 150, InspectITImages.IMG_TIMESTAMP, DefaultDataComparatorEnum.TIMESTAMP), + /** The class column. */ + CLASS("Class", 250, InspectITImages.IMG_CLASS, MethodSensorDataComparatorEnum.CLASS), + /** The package column. */ + PACKAGE("Package", 250, InspectITImages.IMG_PACKAGE, MethodSensorDataComparatorEnum.PACKAGE), + /** Invocation Affiliation. */ + INVOCATION_AFFILLIATION("In Invocations", 120, InspectITImages.IMG_INVOCATION, InvocationAwareDataComparatorEnum.INVOCATION_AFFILIATION); + + /** The name. */ + private String name; + /** The width of the column. */ + private int width; + /** The image descriptor. Can be null */ + private Image image; + /** Comparator for the column. */ + private IDataComparator dataComparator; + + /** + * Default constructor which creates a column enumeration object. + * + * @param name + * The name of the column. + * @param width + * The width of the column. + * @param imageName + * The name of the image. Names are defined in {@link InspectITImages}. + * @param dataComparator + * Comparator for the column. + */ + private Column(String name, int width, String imageName, IDataComparator dataComparator) { + this.name = name; + this.width = width; + this.image = InspectIT.getDefault().getImage(imageName); + this.dataComparator = dataComparator; + } + + /** + * Converts an ordinal into a column. + * + * @param i + * The ordinal. + * @return The appropriate column. + */ + public static Column fromOrd(int i) { + if (i < 0 || i >= Column.values().length) { + throw new IndexOutOfBoundsException("Invalid ordinal"); + } + return Column.values()[i]; + } + + } + + /** + * Default comparator when no sorting is defined. + */ + private final ResultComparator defaultComparator = new ResultComparator<>(DefaultDataComparatorEnum.TIMESTAMP, false); + + /** + * The template object which is send to the server. + */ + private ExceptionSensorData template; + + /** + * The list of invocation sequence data objects which is displayed. + */ + private List exceptionSensorData = new ArrayList(); + + /** + * The limit of the result set. + */ + private int limit = PreferencesUtils.getIntValue(PreferencesConstants.ITEMS_COUNT_TO_SHOW);; + + /** + * Indicates from which point in time data should be shown. + */ + private Date fromDate; + + /** + * Indicates until which point in time data should be shown. + */ + private Date toDate; + + /** + * This data access service is needed because of the ID mappings. + */ + private ICachedDataService cachedDataService; + + /** + * The data access service to access the data on the CMR. + */ + private IExceptionDataAccessService dataAccessService; + + /** + * Result comparator to be used on the server. + */ + private ResultComparator resultComparator = defaultComparator; + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + template = new ExceptionSensorData(); + template.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); + template.setSensorTypeIdent(inputDefinition.getIdDefinition().getSensorTypeId()); + template.setMethodIdent(inputDefinition.getIdDefinition().getMethodId()); + + if (inputDefinition.hasInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.EXCEPTION_TYPE_EXTRAS_MARKER)) { + String throwableType = inputDefinition.getInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.EXCEPTION_TYPE_EXTRAS_MARKER).getThrowableType(); + template.setThrowableType(throwableType); + } + + cachedDataService = inputDefinition.getRepositoryDefinition().getCachedDataService(); + dataAccessService = inputDefinition.getRepositoryDefinition().getExceptionDataAccessService(); + } + + /** + * {@inheritDoc} + */ + public void createColumns(TableViewer tableViewer) { + for (Column column : Column.values()) { + TableViewerColumn viewerColumn = new TableViewerColumn(tableViewer, SWT.NONE); + viewerColumn.getColumn().setMoveable(true); + viewerColumn.getColumn().setResizable(true); + viewerColumn.getColumn().setText(column.name); + viewerColumn.getColumn().setWidth(column.width); + if (null != column.image) { + viewerColumn.getColumn().setImage(column.image); + } + mapTableViewerColumn(column, viewerColumn); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Object getTableInput() { + // this list will be filled with data + return exceptionSensorData; + } + + /** + * {@inheritDoc} + */ + public IContentProvider getContentProvider() { + return new UngroupedExceptionOverviewContentProvider(); + } + + /** + * {@inheritDoc} + */ + public IBaseLabelProvider getLabelProvider() { + return new UngroupedExceptionOverviewLabelProvider(); + } + + /** + * {@inheritDoc} + */ + public ViewerComparator getComparator() { + RemoteTableViewerComparator exceptionViewerComparator = new RemoteTableViewerComparator() { + @Override + protected void sortRemotely(ResultComparator resultComparator) { + if (null != resultComparator) { + UngroupedExceptionOverviewInputController.this.resultComparator = resultComparator; + } else { + UngroupedExceptionOverviewInputController.this.resultComparator = defaultComparator; + } + loadDataFromService(); + } + }; + for (Column column : Column.values()) { + // since it is remote sorting we do not provide local cached data service + ResultComparator resultComparator = new ResultComparator(column.dataComparator); + exceptionViewerComparator.addColumn(getMappedTableViewerColumn(column).getColumn(), resultComparator); + } + + return exceptionViewerComparator; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getPreferenceIds() { + Set preferences = EnumSet.noneOf(PreferenceId.class); + if (getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) { + preferences.add(PreferenceId.CLEAR_BUFFER); + preferences.add(PreferenceId.LIVEMODE); + } + preferences.add(PreferenceId.UPDATE); + preferences.add(PreferenceId.ITEMCOUNT); + preferences.add(PreferenceId.TIMELINE); + return preferences; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canOpenInput(List data) { + if (data.isEmpty()) { + return true; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void setLimit(int limit) { + this.limit = limit; + } + + /** + * {@inheritDoc} + */ + @Override + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + switch (preferenceEvent.getPreferenceId()) { + case TIMELINE: + Map preferenceMap = preferenceEvent.getPreferenceMap(); + if (preferenceMap.containsKey(PreferenceId.TimeLine.FROM_DATE_ID)) { + fromDate = (Date) preferenceMap.get(PreferenceId.TimeLine.FROM_DATE_ID); + } + if (preferenceMap.containsKey(PreferenceId.TimeLine.TO_DATE_ID)) { + toDate = (Date) preferenceMap.get(PreferenceId.TimeLine.TO_DATE_ID); + } + break; + default: + break; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void doRefresh(IProgressMonitor monitor, IRootEditor rootEditor) { + monitor.beginTask("Updating the Ungrouped Exception Overview", IProgressMonitor.UNKNOWN); + monitor.subTask("Retrieving the Ungrouped Exception Overview"); + + loadDataFromService(); + + monitor.done(); + } + + /** + * Reloads the data from the service. + */ + private void loadDataFromService() { + List exData = null; + + // if fromDate and toDate are set, then we retrieve only the data for + // this time interval + if (null != fromDate && null != toDate) { + exData = dataAccessService.getUngroupedExceptionOverview(template, limit, fromDate, toDate, resultComparator); + } else { + exData = dataAccessService.getUngroupedExceptionOverview(template, limit, resultComparator); + } + exceptionSensorData.clear(); + + if (null != exData) { + exceptionSensorData.addAll(exData); + } + + } + + /** + * {@inheritDoc} + */ + @Override + public void doubleClick(DoubleClickEvent event) { + final StructuredSelection selection = (StructuredSelection) event.getSelection(); + if (!selection.isEmpty()) { + try { + PlatformUI.getWorkbench().getProgressService().busyCursorWhile(new IRunnableWithProgress() { + public void run(final IProgressMonitor monitor) { + monitor.beginTask("Retrieving Exception Tree", IProgressMonitor.UNKNOWN); + ExceptionSensorData data = (ExceptionSensorData) selection.getFirstElement(); + List exceptionSensorDataList = dataAccessService.getExceptionTree(data); + final List finalSensorDataList = new ArrayList(); + + finalSensorDataList.add(exceptionSensorDataList.get(0)); + + Display.getDefault().asyncExec(new Runnable() { + public void run() { + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + IWorkbenchPage page = window.getActivePage(); + IRootEditor rootEditor = (IRootEditor) page.getActiveEditor(); + rootEditor.setDataInput(finalSensorDataList); + } + }); + monitor.done(); + } + }); + } catch (InvocationTargetException e) { + MessageDialog.openError(Display.getDefault().getActiveShell().getShell(), "Error", e.getCause().toString()); + } catch (InterruptedException e) { + MessageDialog.openInformation(Display.getDefault().getActiveShell().getShell(), "Cancelled", e.getCause().toString()); + } + } + } + + /** + * The label provider for this view. + * + * @author Eduard Tudenhoefner + * + */ + private final class UngroupedExceptionOverviewLabelProvider extends StyledCellIndexLabelProvider { + + /** + * {@inheritDoc} + */ + @Override + protected StyledString getStyledText(Object element, int index) { + ExceptionSensorData data = (ExceptionSensorData) element; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + Column enumId = Column.fromOrd(index); + + return getStyledTextForColumn(data, methodIdent, enumId); + } + + } + + /** + * The content provider for this view. + * + * @author Eduard Tudenhoefner + * + */ + private static final class UngroupedExceptionOverviewContentProvider implements IStructuredContentProvider { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public Object[] getElements(Object inputElement) { + List exceptionSensorData = (List) inputElement; + return exceptionSensorData.toArray(); + } + + /** + * {@inheritDoc} + */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + + } + + /** + * Returns the styled text for a specific column. + * + * @param data + * The data object to extract the information from. + * @param methodIdent + * The method ident object. + * @param enumId + * The enumeration ID. + * @return The styled string containing the information from the data object. + */ + private StyledString getStyledTextForColumn(ExceptionSensorData data, MethodIdent methodIdent, Column enumId) { + switch (enumId) { + case PACKAGE: + if (methodIdent.getPackageName() != null && !methodIdent.getPackageName().equals("")) { + return new StyledString(methodIdent.getPackageName()); + } else { + return new StyledString("(default)"); + } + case CLASS: + return new StyledString(methodIdent.getClassName()); + case TIMESTAMP: + return new StyledString(NumberFormatter.formatTime(data.getTimeStamp())); + case INVOCATION_AFFILLIATION: + int percentage = (int) (data.getInvocationAffiliationPercentage() * 100); + int invocations = 0; + if (null != data.getInvocationParentsIdSet()) { + invocations = data.getInvocationParentsIdSet().size(); + } + return TextFormatter.getInvocationAffilliationPercentageString(percentage, invocations); + default: + return new StyledString("error"); + } + } + + /** + * {@inheritDoc} + */ + public String getReadableString(Object object) { + if (object instanceof ExceptionSensorData) { + ExceptionSensorData data = (ExceptionSensorData) object; + StringBuilder sb = new StringBuilder(); + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + for (Column column : Column.values()) { + sb.append(getStyledTextForColumn(data, methodIdent, column).toString()); + sb.append('\t'); + } + return sb.toString(); + } + throw new RuntimeException("Could not create the human readable string!"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getColumnValues(Object object) { + if (object instanceof ExceptionSensorData) { + ExceptionSensorData data = (ExceptionSensorData) object; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + List values = new ArrayList(); + for (Column column : Column.values()) { + values.add(getStyledTextForColumn(data, methodIdent, column).toString()); + } + return values; + } + throw new RuntimeException("Could not create the column values!"); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/testers/ActiveSubViewTester.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/testers/ActiveSubViewTester.java new file mode 100644 index 000000000..7fc2e3df4 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/testers/ActiveSubViewTester.java @@ -0,0 +1,73 @@ +package info.novatec.inspectit.rcp.editor.testers; + +import info.novatec.inspectit.rcp.editor.ISubView; +import info.novatec.inspectit.rcp.editor.composite.AbstractCompositeSubView; +import info.novatec.inspectit.rcp.editor.graph.GraphSubView; +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.editor.search.ISearchExecutor; +import info.novatec.inspectit.rcp.editor.table.TableSubView; +import info.novatec.inspectit.rcp.editor.tree.SteppingTreeSubView; +import info.novatec.inspectit.rcp.editor.tree.TreeSubView; + +import org.eclipse.core.expressions.PropertyTester; + +/** + * @author Patrice Bouillet + * + */ +public class ActiveSubViewTester extends PropertyTester { + + /** + * {@inheritDoc} + */ + public boolean test(Object receiver, String property, Object[] args, Object expectedValue) { + if (receiver instanceof AbstractRootEditor) { + AbstractRootEditor rootEditor = (AbstractRootEditor) receiver; + if ("activeSubView".equals(property)) { + if ("treeSubView".equals(expectedValue)) { + return rootEditor.getActiveSubView() instanceof TreeSubView; + } else if ("tableSubView".equals(expectedValue)) { + return rootEditor.getActiveSubView() instanceof TableSubView; + } else if ("notNull".equals(expectedValue)) { + return rootEditor.getActiveSubView() != null; + } + } else if ("hasSubView".equals(property)) { + if ("steppingTreeSubView".equals(expectedValue)) { + return isSubViewExisting(rootEditor.getSubView(), SteppingTreeSubView.class); + } else if ("graphSubView".equals(expectedValue)) { + return isSubViewExisting(rootEditor.getSubView(), GraphSubView.class); + } else if ("compositeSubView".equals(expectedValue)) { + return isSubViewExisting(rootEditor.getSubView(), AbstractCompositeSubView.class); + } else if ("searchExecutor".equals(expectedValue)) { + return isSubViewExisting(rootEditor.getSubView(), ISearchExecutor.class); + } + } + } + + return false; + } + + /** + * Returns if the given sub view is a instance of given sub-view class or if there is a sub-view + * of this class in case composite sub-view is provided. This is a recursive method. + * + * @param subView + * Sub-view to check. + * @param subViewClass + * Class to search for. + * @return Returns true if the wanted class is found. + */ + private boolean isSubViewExisting(ISubView subView, Class subViewClass) { + if (subViewClass.isInstance(subView)) { + return true; + } else if (subView instanceof AbstractCompositeSubView) { + AbstractCompositeSubView compositeSubView = (AbstractCompositeSubView) subView; + for (ISubView viewInCompositeSubView : compositeSubView.getSubViews()) { + if (isSubViewExisting(viewInCompositeSubView, subViewClass)) { + return true; + } + } + } + return false; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/testers/AddToSteppingObjectsTester.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/testers/AddToSteppingObjectsTester.java new file mode 100644 index 000000000..f0055b162 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/testers/AddToSteppingObjectsTester.java @@ -0,0 +1,47 @@ +package info.novatec.inspectit.rcp.editor.testers; + +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; + +import java.util.Iterator; + +import org.eclipse.core.expressions.PropertyTester; +import org.eclipse.jface.viewers.StructuredSelection; + +/** + * Tester checks if there is at least one object in the selection that could be added to the + * steppable objects list. NOte that empty + * {@link info.novatec.inspectit.communication.data.InvocationSequenceData} and + * {@link HttpTimerData} can not be added. + * + * @author Ivan Senic + * + */ +public class AddToSteppingObjectsTester extends PropertyTester { + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public boolean test(Object receiver, String property, Object[] args, Object expectedValue) { + boolean correctSelection = false; + if (receiver instanceof StructuredSelection) { + Iterator it = ((StructuredSelection) receiver).iterator(); + while (it.hasNext()) { + Object nextObject = it.next(); + if (nextObject instanceof MethodSensorData) { + if (nextObject instanceof HttpTimerData) { + continue; + } else { + correctSelection = true; + break; + } + } + } + } + + return correctSelection == ((Boolean) expectedValue).booleanValue(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/testers/MaximizeMinimizeTester.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/testers/MaximizeMinimizeTester.java new file mode 100644 index 000000000..8753b819a --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/testers/MaximizeMinimizeTester.java @@ -0,0 +1,31 @@ +package info.novatec.inspectit.rcp.editor.testers; + +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; + +import org.eclipse.core.expressions.PropertyTester; + +/** + * Tester that checks if the active sub-view can be maximized/minimized. + * + * @author Ivan Senic + * + */ +public class MaximizeMinimizeTester extends PropertyTester { + + /** + * {@inheritDoc} + */ + @Override + public boolean test(Object receiver, String property, Object[] args, Object expectedValue) { + if (receiver instanceof AbstractRootEditor) { + AbstractRootEditor rootEditor = (AbstractRootEditor) receiver; + if ("canMaximize".equals(property)) { + return rootEditor.canMaximizeActiveSubView(); + } else if ("canMinimize".equals(property)) { + return rootEditor.canMinimizeActiveSubView(); + } + } + return false; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/testers/NavigationTester.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/testers/NavigationTester.java new file mode 100644 index 000000000..564410fd1 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/testers/NavigationTester.java @@ -0,0 +1,128 @@ +package info.novatec.inspectit.rcp.editor.testers; + +import info.novatec.inspectit.cmr.model.SensorTypeIdent; +import info.novatec.inspectit.communication.data.AggregatedTimerData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.InvocationAwareData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.model.SensorTypeEnum; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.Iterator; +import java.util.List; + +import org.eclipse.core.expressions.PropertyTester; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.PlatformUI; + +/** + * Tester for all navigations. + * + * @author Ivan Senic + * + */ +public class NavigationTester extends PropertyTester { + + /** + * {@inheritDoc} + */ + @Override + public boolean test(Object receiver, String property, Object[] args, Object expectedValue) { + if ("canNavigateToPlotting".equals(property)) { + if (receiver instanceof StructuredSelection) { + StructuredSelection selection = (StructuredSelection) receiver; + Object selectedObject = selection.getFirstElement(); + if (selectedObject instanceof InvocationSequenceData) { + // only navigate if a real TimerData is provided (not for HttpTimerData or SQL) + TimerData timerData = ((InvocationSequenceData) selectedObject).getTimerData(); + return isTimerSensorBounded(timerData) && timerData.isCharting(); + } else if (selectedObject instanceof TimerData) { + return isTimerSensorBounded((TimerData) selectedObject) && ((TimerData) selectedObject).isCharting(); + } + } + } else if ("canNavigateToInvocations".equals(property)) { + if (receiver instanceof StructuredSelection) { + StructuredSelection selection = (StructuredSelection) receiver; + for (Iterator iterator = selection.iterator(); iterator.hasNext();) { + Object selectedObject = iterator.next(); + if (selectedObject instanceof InvocationAwareData) { + InvocationAwareData invocationAwareData = (InvocationAwareData) selectedObject; + if (!invocationAwareData.isOnlyFoundOutsideInvocations()) { + return true; + } + } + } + } + } else if ("canNavigateToExceptionType".equals(property)) { + StructuredSelection selection = (StructuredSelection) receiver; + Object selectedObject = selection.getFirstElement(); + if (selectedObject instanceof InvocationSequenceData) { + List exceptions = ((InvocationSequenceData) selectedObject).getExceptionSensorDataObjects(); + if (null != exceptions && !exceptions.isEmpty()) { + for (ExceptionSensorData exceptionSensorData : exceptions) { + if (null != exceptionSensorData.getThrowableType()) { + return true; + } + } + } + } else if (selectedObject instanceof ExceptionSensorData) { + return ((ExceptionSensorData) selectedObject).getThrowableType() != null; + } + } else if ("canNavigateToAggregatedTimerData".equals(property)) { + if (receiver instanceof StructuredSelection) { + StructuredSelection selection = (StructuredSelection) receiver; + Object selectedObject = selection.getFirstElement(); + if (selectedObject instanceof InvocationSequenceData) { + // only navigate if a real TimerData is provided (not for HttpTimerData or SQL) + TimerData timerData = ((InvocationSequenceData) selectedObject).getTimerData(); + return isTimerSensorBounded(timerData); + } else if (selectedObject instanceof TimerData) { + return isTimerSensorBounded((TimerData) selectedObject); + } + } + } else if ("canNavigateToAggregatedSqlData".equals(property)) { + if (receiver instanceof StructuredSelection) { + StructuredSelection selection = (StructuredSelection) receiver; + Object selectedObject = selection.getFirstElement(); + if (selectedObject instanceof InvocationSequenceData) { + return null != ((InvocationSequenceData) selectedObject).getSqlStatementData(); + } else if (selectedObject instanceof SqlStatementData) { + return true; + } + } + } + + return false; + } + + /** + * Checks if the given timer data has a sensor type that equals {@link SensorTypeEnum#TIMER} or + * {@link SensorTypeEnum#AVERAGE_TIMER}, so that a special navigation types are possible or not. + * + * @param timerData + * {@link TimerData} to check. + * @return True if given object is of a TimerData class and mentioned sensor types are + * registered. False otherwise. + */ + private boolean isTimerSensorBounded(TimerData timerData) { + if (null == timerData || (!timerData.getClass().equals(TimerData.class) && !timerData.getClass().equals(AggregatedTimerData.class))) { + return false; + } + + IEditorPart editor = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor(); + if (editor instanceof AbstractRootEditor) { + RepositoryDefinition repositoryDefinition = ((AbstractRootEditor) editor).getInputDefinition().getRepositoryDefinition(); + SensorTypeIdent sensorTypeIdent = repositoryDefinition.getCachedDataService().getSensorTypeIdentForId(timerData.getSensorTypeIdent()); + if (null != sensorTypeIdent) { + SensorTypeEnum sensorTypeEnum = SensorTypeEnum.get(sensorTypeIdent.getFullyQualifiedClassName()); + return sensorTypeEnum == SensorTypeEnum.TIMER || sensorTypeEnum == SensorTypeEnum.AVERAGE_TIMER; + } + } + + return false; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/testers/SubViewClassificationTester.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/testers/SubViewClassificationTester.java new file mode 100644 index 000000000..312418db0 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/testers/SubViewClassificationTester.java @@ -0,0 +1,55 @@ +package info.novatec.inspectit.rcp.editor.testers; + +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.editor.root.SubViewClassificationController.SubViewClassification; +import info.novatec.inspectit.rcp.editor.table.TableSubView; +import info.novatec.inspectit.rcp.editor.tree.TreeSubView; + +import org.eclipse.core.expressions.PropertyTester; + +/** + * Tester for testing the sub view classification. The tester can test if the view is master or + * slave. + * + * @author Ivan Senic + * + */ +public class SubViewClassificationTester extends PropertyTester { + + /** + * {@inheritDoc} + */ + @Override + public boolean test(Object receiver, String property, Object[] args, Object expectedValue) { + if (receiver instanceof AbstractRootEditor) { + AbstractRootEditor rootEditor = (AbstractRootEditor) receiver; + if (rootEditor.getActiveSubView() instanceof TableSubView) { + TableSubView tableSubView = (TableSubView) rootEditor.getActiveSubView(); + if ("master".equals(expectedValue)) { + if (tableSubView.getTableInputController().getSubViewClassification() == SubViewClassification.MASTER) { + return true; + } + } else if ("slave".equals(expectedValue)) { + if (tableSubView.getTableInputController().getSubViewClassification() == SubViewClassification.SLAVE) { + return true; + } + } + } else if (rootEditor.getActiveSubView() instanceof TreeSubView) { + TreeSubView treeSubView = (TreeSubView) rootEditor.getActiveSubView(); + if ("master".equals(expectedValue)) { + if (treeSubView.getTreeInputController().getSubViewClassification() == SubViewClassification.MASTER) { + return true; + } + } else if ("slave".equals(expectedValue)) { + if (treeSubView.getTreeInputController().getSubViewClassification() == SubViewClassification.SLAVE) { + return true; + } + } + + } + } + + return false; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/text/TextSubView.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/TextSubView.java new file mode 100644 index 000000000..ab9b1be93 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/TextSubView.java @@ -0,0 +1,182 @@ +package info.novatec.inspectit.rcp.editor.text; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.editor.AbstractSubView; +import info.novatec.inspectit.rcp.editor.ISubView; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.text.input.TextInputController; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * This class is for text views. + * + * @author Eduard Tudenhoefner + * @author Patrice Bouillet + * + */ +public class TextSubView extends AbstractSubView { + + /** + * The {@link Composite}. + */ + private Composite composite; + + /** + * The {@link TextInputController}. + */ + private TextInputController textInputController; + + /** + * Defines if a refresh job is currently already executing. + */ + private volatile boolean jobInSchedule = false; + + /** + * The constructor accepting one parameter. + * + * @param textInputController + * An instance of the {@link TextInputController}. + */ + public TextSubView(TextInputController textInputController) { + Assert.isNotNull(textInputController); + + this.textInputController = textInputController; + } + + /** + * {@inheritDoc} + */ + @Override + public void init() { + textInputController.setInputDefinition(getRootEditor().getInputDefinition()); + } + + /** + * {@inheritDoc} + */ + public void createPartControl(Composite parent, FormToolkit toolkit) { + composite = toolkit.createComposite(parent); + composite.setLayout(new GridLayout(1, false)); + textInputController.createPartControl(composite, toolkit); + } + + /** + * {@inheritDoc} + */ + public void doRefresh() { + if (checkDisposed()) { + return; + } + + if (!jobInSchedule) { + jobInSchedule = true; + + Job job = new Job(getDataLoadingJobName()) { + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + textInputController.doRefresh(); + return Status.OK_STATUS; + } catch (Throwable throwable) { // NOPMD + throw new RuntimeException("Unknown exception occurred trying to refresh the view.", throwable); + } finally { + jobInSchedule = false; + } + } + }; + job.schedule(); + } + } + + /** + * {@inheritDoc} + */ + public Control getControl() { + return composite; + } + + /** + * {@inheritDoc} + */ + public ISelectionProvider getSelectionProvider() { + return null; + } + + /** + * Returns an instance of a {@link TextInputController}. + * + * @return An instance of a {@link TextInputController}. + */ + public TextInputController getTextInputController() { + return textInputController; + } + + /** + * {@inheritDoc} + */ + public void setDataInput(List data) { + if (checkDisposed()) { + return; + } + + textInputController.setDataInput(data); + } + + /** + * {@inheritDoc} + */ + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + } + + /** + * {@inheritDoc} + */ + public Set getPreferenceIds() { + return Collections.emptySet(); + } + + /** + * {@inheritDoc} + */ + @Override + public ISubView getSubViewWithInputController(Class inputControllerClass) { + if (Objects.equals(inputControllerClass, textInputController.getClass())) { + return this; + } + return null; + } + + /** + * Returns true if the composite in the sub-view is disposed. False otherwise. + * + * @return Returns true if the composite in the sub-view is disposed. False otherwise. + */ + private boolean checkDisposed() { + return composite.isDisposed(); + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + textInputController.dispose(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/AbstractTextInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/AbstractTextInputController.java new file mode 100644 index 000000000..7b31f46a4 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/AbstractTextInputController.java @@ -0,0 +1,149 @@ +package info.novatec.inspectit.rcp.editor.text.input; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Section; + +/** + * General implementation of {@link TextInputController}, where most of the methods are not doing + * anything. Classes that extend should override the methods they want to define the proper + * behavior. + * + * @author Ivan Senic + * + */ +public abstract class AbstractTextInputController implements TextInputController { + + /** + * The input definition. + */ + private InputDefinition inputDefinition; + + /** + * The {@link HashMap} containing the different sections. + */ + protected Map sections = new HashMap(); + + /** + * {@inheritDoc} + */ + public void setInputDefinition(InputDefinition inputDefinition) { + Assert.isNotNull(inputDefinition); + + this.inputDefinition = inputDefinition; + } + + /** + * Returns the input definition. + * + * @return The input definition. + */ + protected InputDefinition getInputDefinition() { + Assert.isNotNull(inputDefinition); + + return inputDefinition; + } + + /** + * Adds a section to bundle some content. + * + * @param parent + * The parent used to draw the elements to. + * @param toolkit + * The form toolkit. + * @param sectionTitle + * The section title + */ + protected void addSection(Composite parent, FormToolkit toolkit, String sectionTitle) { + Section section = toolkit.createSection(parent, Section.TITLE_BAR); + section.setText(sectionTitle); + section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + Composite sectionComposite = toolkit.createComposite(section); + GridLayout gridLayout = new GridLayout(4, false); + gridLayout.marginLeft = 5; + gridLayout.marginTop = 5; + sectionComposite.setLayout(gridLayout); + section.setClient(sectionComposite); + + if (!sections.containsKey(sectionTitle)) { + sections.put(sectionTitle, sectionComposite); + } + } + + /** + * Adds an item to the specified section. + * + * @param toolkit + * The form toolkit. + * @param sectionTitle + * The section title. + * @param text + * The text which will be shown. + */ + protected void addItemToSection(FormToolkit toolkit, String sectionTitle, String text) { + if (sections.containsKey(sectionTitle)) { + Label label = toolkit.createLabel(sections.get(sectionTitle), text); + label.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL)); + } + } + + /** + * Adds an item to the specified section. + * + * @param toolkit + * The form toolkit. + * @param sectionTitle + * The section title. + * @param text + * The text which will be shown. + * @param minColumnWidth + * the minimum width of the column. + */ + protected void addItemToSection(FormToolkit toolkit, String sectionTitle, String text, int minColumnWidth) { + if (sections.containsKey(sectionTitle)) { + Label label = toolkit.createLabel(sections.get(sectionTitle), text, SWT.LEFT); + label.setLayoutData(new GridData(minColumnWidth, SWT.DEFAULT)); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void createPartControl(Composite parent, FormToolkit toolkit) { + } + + /** + * {@inheritDoc} + */ + @Override + public void doRefresh() { + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + } + + /** + * {@inheritDoc} + */ + @Override + public void setDataInput(List data) { + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/ClassesInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/ClassesInputController.java new file mode 100644 index 000000000..5e146ee2b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/ClassesInputController.java @@ -0,0 +1,118 @@ +package info.novatec.inspectit.rcp.editor.text.input; + +import info.novatec.inspectit.cmr.service.IGlobalDataAccessService; +import info.novatec.inspectit.communication.data.ClassLoadingInformationData; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * This class represents the textual view of the {@link ClassLoadingInformation} sensor-type. + * + * @author Eduard Tudenhoefner + * + */ +public class ClassesInputController extends AbstractTextInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.text.classes"; + + /** + * The name for the section. + */ + private static final String SECTION_CLASSES = "Classes"; + + /** + * The template of the {@link ClassLoadingInformationData} object. + */ + private ClassLoadingInformationData classLoadingObj; + + /** + * The label for loaded classes. + */ + private Label loadedClassCount; + + /** + * The label for total loaded classes. + */ + private Label totalLoadedClassCount; + + /** + * The label for unloaded classes. + */ + private Label unloadedClassCount; + + /** + * The global data access service. + */ + private IGlobalDataAccessService dataAccessService; + + /** + * {@inheritDoc} + */ + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + classLoadingObj = new ClassLoadingInformationData(); + classLoadingObj.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); + + dataAccessService = inputDefinition.getRepositoryDefinition().getGlobalDataAccessService(); + } + + /** + * {@inheritDoc} + */ + public void createPartControl(Composite parent, FormToolkit toolkit) { + addSection(parent, toolkit, SECTION_CLASSES); + + if (sections.containsKey(SECTION_CLASSES)) { + // creates the labels + addItemToSection(toolkit, SECTION_CLASSES, "Current loaded classes: "); + loadedClassCount = toolkit.createLabel(sections.get(SECTION_CLASSES), "n/a"); + loadedClassCount.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + addItemToSection(toolkit, SECTION_CLASSES, "Total loaded classes: "); + totalLoadedClassCount = toolkit.createLabel(sections.get(SECTION_CLASSES), "n/a"); + totalLoadedClassCount.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + addItemToSection(toolkit, SECTION_CLASSES, "Total unloaded classes: "); + unloadedClassCount = toolkit.createLabel(sections.get(SECTION_CLASSES), "n/a"); + unloadedClassCount.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + } + } + + /** + * {@inheritDoc} + */ + public void doRefresh() { + final ClassLoadingInformationData data = (ClassLoadingInformationData) dataAccessService.getLastDataObject(classLoadingObj); + + if (null != data) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + // updates the labels + int count = data.getCount(); + loadedClassCount.setText(NumberFormatter.formatInteger(data.getTotalLoadedClassCount() / count)); + totalLoadedClassCount.setText(NumberFormatter.formatLong(data.getTotalTotalLoadedClassCount() / count)); + unloadedClassCount.setText(NumberFormatter.formatLong(data.getTotalUnloadedClassCount() / count)); + } + }); + } + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/CpuInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/CpuInputController.java new file mode 100644 index 000000000..bcb275c3c --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/CpuInputController.java @@ -0,0 +1,120 @@ +package info.novatec.inspectit.rcp.editor.text.input; + +import info.novatec.inspectit.cmr.service.IGlobalDataAccessService; +import info.novatec.inspectit.communication.data.CpuInformationData; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * This class represents the textual view of the {@link CpuInformation} sensor-type. + * + * @author Eduard Tudenhoefner + * + */ +public class CpuInputController extends AbstractTextInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.text.cpu"; + + /** + * The name of the section. + */ + private static final String SECTION_CPU = "CPU"; + + /** + * The string representing that something is not available. + */ + private static final String NOT_AVAILABLE = "N/A"; + + /** + * The template of the {@link CpuInformationData} object. + */ + private CpuInformationData cpuObj; + + /** + * The label for the cpu usage. + */ + private Label cpuUsage; + + /** + * The label for the process cpu time. + */ + private Label processCpuTime; + + /** + * The global data access service. + */ + private IGlobalDataAccessService dataAccessService; + + /** + * {@inheritDoc} + */ + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + cpuObj = new CpuInformationData(); + cpuObj.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); + + dataAccessService = inputDefinition.getRepositoryDefinition().getGlobalDataAccessService(); + } + + /** + * {@inheritDoc} + */ + public void createPartControl(Composite parent, FormToolkit toolkit) { + addSection(parent, toolkit, SECTION_CPU); + + if (sections.containsKey(SECTION_CPU)) { + // creates the labels + addItemToSection(toolkit, SECTION_CPU, "Cpu Usage: "); + cpuUsage = toolkit.createLabel(sections.get(SECTION_CPU), "n/a", SWT.LEFT); + cpuUsage.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + addItemToSection(toolkit, SECTION_CPU, "Process Cpu Time: "); + processCpuTime = toolkit.createLabel(sections.get(SECTION_CPU), "n/a", SWT.LEFT); + processCpuTime.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + } + } + + /** + * {@inheritDoc} + */ + public void doRefresh() { + final CpuInformationData data = (CpuInformationData) dataAccessService.getLastDataObject(cpuObj); + + if (null != data) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + int count = data.getCount(); + if (data.getTotalCpuUsage() > 0) { + cpuUsage.setText(NumberFormatter.formatCpuPercent(data.getTotalCpuUsage() / count)); + } else { + cpuUsage.setText(NOT_AVAILABLE); + } + if (data.getProcessCpuTime() > 0) { + processCpuTime.setText(NumberFormatter.formatNanosToSeconds(data.getProcessCpuTime())); + } else { + processCpuTime.setText(NOT_AVAILABLE); + } + } + }); + } + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/MemoryInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/MemoryInputController.java new file mode 100644 index 000000000..df2008492 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/MemoryInputController.java @@ -0,0 +1,225 @@ +package info.novatec.inspectit.rcp.editor.text.input; + +import info.novatec.inspectit.cmr.service.IGlobalDataAccessService; +import info.novatec.inspectit.communication.data.MemoryInformationData; +import info.novatec.inspectit.communication.data.SystemInformationData; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * This class represents the textual view of the {@link MemoryInformation} sensor-type. + * + * @author Eduard Tudenhoefner + * + */ +public class MemoryInputController extends AbstractTextInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.text.memory"; + + /** + * The name of the section. + */ + private static final String SECTION_MEMORY = "Memory"; + + /** + * The string representing that something is not available. + */ + private static final String NOT_AVAILABLE = "N/A"; + + /** + * The template of the {@link MemoryInformationData} object. + */ + private MemoryInformationData memoryObj; + + /** + * The template of the {@link SystemInformationData} object. + */ + private SystemInformationData systemObj; + + /** + * The label for free physical memory. + */ + private Label freePhysMemory; + + /** + * The label for free swap space. + */ + private Label freeSwapSpace; + + /** + * The label for committed heap memory size. + */ + private Label committedHeapMemorySize; + + /** + * The label for committed non-heap memory size. + */ + private Label committedNonHeapMemorySize; + + /** + * The label for used heap memory size. + */ + private Label usedHeapMemorySize; + + /** + * The label for used non-heap memory size. + */ + private Label usedNonHeapMemorySize; + + /** + * The global data access service. + */ + private IGlobalDataAccessService dataAccessService; + + /** + * {@inheritDoc} + */ + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + memoryObj = new MemoryInformationData(); + memoryObj.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); + + systemObj = new SystemInformationData(); + systemObj.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); + + dataAccessService = inputDefinition.getRepositoryDefinition().getGlobalDataAccessService(); + } + + /** + * {@inheritDoc} + */ + public void createPartControl(Composite parent, FormToolkit toolkit) { + addSection(parent, toolkit, SECTION_MEMORY); + + SystemInformationData systemData = (SystemInformationData) dataAccessService.getLastDataObject(systemObj); + if (systemData != null) { + // adds some static informations + addItemToSection(toolkit, SECTION_MEMORY, "Max heap size: "); + if (systemData.getMaxHeapMemorySize() > 0) { + addItemToSection(toolkit, SECTION_MEMORY, NumberFormatter.formatBytesToKBytes(systemData.getMaxHeapMemorySize())); + } else { + addItemToSection(toolkit, SECTION_MEMORY, NOT_AVAILABLE); + } + addItemToSection(toolkit, SECTION_MEMORY, "Max non-heap size: "); + if (systemData.getMaxNonHeapMemorySize() > 0) { + addItemToSection(toolkit, SECTION_MEMORY, NumberFormatter.formatBytesToKBytes(systemData.getMaxNonHeapMemorySize())); + } else { + addItemToSection(toolkit, SECTION_MEMORY, NOT_AVAILABLE); + } + addItemToSection(toolkit, SECTION_MEMORY, "Total physical memory: "); + if (systemData.getTotalPhysMemory() > 0) { + addItemToSection(toolkit, SECTION_MEMORY, NumberFormatter.formatBytesToKBytes(systemData.getTotalPhysMemory())); + } else { + addItemToSection(toolkit, SECTION_MEMORY, NOT_AVAILABLE); + } + addItemToSection(toolkit, SECTION_MEMORY, "Total swap space: "); + if (systemData.getTotalSwapSpace() > 0) { + addItemToSection(toolkit, SECTION_MEMORY, NumberFormatter.formatBytesToKBytes(systemData.getTotalSwapSpace())); + } else { + addItemToSection(toolkit, SECTION_MEMORY, NOT_AVAILABLE); + } + } else { + // if no static informations available + addItemToSection(toolkit, SECTION_MEMORY, "Max heap size: "); + addItemToSection(toolkit, SECTION_MEMORY, NOT_AVAILABLE); + addItemToSection(toolkit, SECTION_MEMORY, "Max non-heap size: "); + addItemToSection(toolkit, SECTION_MEMORY, NOT_AVAILABLE); + addItemToSection(toolkit, SECTION_MEMORY, "Total physical memory: "); + addItemToSection(toolkit, SECTION_MEMORY, NOT_AVAILABLE); + addItemToSection(toolkit, SECTION_MEMORY, "Total swap space: "); + addItemToSection(toolkit, SECTION_MEMORY, NOT_AVAILABLE); + } + + if (sections.containsKey(SECTION_MEMORY)) { + // creates some labels + addItemToSection(toolkit, SECTION_MEMORY, "Free physical memory: "); + freePhysMemory = toolkit.createLabel(sections.get(SECTION_MEMORY), NOT_AVAILABLE, SWT.LEFT); + freePhysMemory.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + addItemToSection(toolkit, SECTION_MEMORY, "Free swap space: "); + freeSwapSpace = toolkit.createLabel(sections.get(SECTION_MEMORY), NOT_AVAILABLE, SWT.LEFT); + freeSwapSpace.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + addItemToSection(toolkit, SECTION_MEMORY, "Committed heap size: "); + committedHeapMemorySize = toolkit.createLabel(sections.get(SECTION_MEMORY), NOT_AVAILABLE, SWT.LEFT); + committedHeapMemorySize.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + addItemToSection(toolkit, SECTION_MEMORY, "Committed non-heap size: "); + committedNonHeapMemorySize = toolkit.createLabel(sections.get(SECTION_MEMORY), NOT_AVAILABLE, SWT.LEFT); + committedNonHeapMemorySize.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + addItemToSection(toolkit, SECTION_MEMORY, "Used heap size: "); + usedHeapMemorySize = toolkit.createLabel(sections.get(SECTION_MEMORY), NOT_AVAILABLE, SWT.LEFT); + usedHeapMemorySize.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + addItemToSection(toolkit, SECTION_MEMORY, "Used non-heap size: "); + usedNonHeapMemorySize = toolkit.createLabel(sections.get(SECTION_MEMORY), NOT_AVAILABLE, SWT.LEFT); + usedNonHeapMemorySize.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + } + } + + /** + * {@inheritDoc} + */ + public void doRefresh() { + final MemoryInformationData data = (MemoryInformationData) dataAccessService.getLastDataObject(memoryObj); + + if (null != data) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + // updates the labels + int count = data.getCount(); + if (data.getTotalFreePhysMemory() > 0) { + freePhysMemory.setText(NumberFormatter.formatBytesToKBytes(data.getTotalFreePhysMemory() / count)); + } else { + freePhysMemory.setText(NOT_AVAILABLE); + } + if (data.getTotalFreeSwapSpace() > 0) { + freeSwapSpace.setText(NumberFormatter.formatBytesToKBytes(data.getTotalFreeSwapSpace() / count)); + } else { + freeSwapSpace.setText(NOT_AVAILABLE); + } + if (data.getTotalComittedHeapMemorySize() > 0) { + committedHeapMemorySize.setText(NumberFormatter.formatBytesToKBytes(data.getTotalComittedHeapMemorySize() / count)); + } else { + committedHeapMemorySize.setText(NOT_AVAILABLE); + } + if (data.getTotalComittedNonHeapMemorySize() > 0) { + committedNonHeapMemorySize.setText(NumberFormatter.formatBytesToKBytes(data.getTotalComittedNonHeapMemorySize() / count)); + } else { + committedNonHeapMemorySize.setText(NOT_AVAILABLE); + } + if (data.getTotalUsedHeapMemorySize() > 0) { + usedHeapMemorySize.setText(NumberFormatter.formatBytesToKBytes(data.getTotalUsedHeapMemorySize() / count)); + } else { + usedHeapMemorySize.setText(NOT_AVAILABLE); + } + if (data.getTotalUsedNonHeapMemorySize() > 0) { + usedNonHeapMemorySize.setText(NumberFormatter.formatBytesToKBytes(data.getTotalUsedNonHeapMemorySize() / count)); + } else { + usedNonHeapMemorySize.setText(NOT_AVAILABLE); + } + } + }); + } + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/SqlInvocSummaryTextInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/SqlInvocSummaryTextInputController.java new file mode 100644 index 000000000..904d47f13 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/SqlInvocSummaryTextInputController.java @@ -0,0 +1,425 @@ +package info.novatec.inspectit.rcp.editor.text.input; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.InvocationSequenceDataHelper; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; +import info.novatec.inspectit.rcp.formatter.ColorFormatter; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.mutable.MutableDouble; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.resource.ResourceManager; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Layout; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.forms.HyperlinkSettings; +import org.eclipse.ui.forms.events.HyperlinkAdapter; +import org.eclipse.ui.forms.events.HyperlinkEvent; +import org.eclipse.ui.forms.widgets.FormText; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * The small summary below the SQL invocation overview. + * + * @author Ivan Senic + * + */ +public class SqlInvocSummaryTextInputController extends AbstractTextInputController { + + /** + * Constant for the slowest80/20 color id. + */ + private static final String SLOWEST8020_COLOR = "slowest8020Color"; + + /** + * Link for slowest 80% sqls. + */ + private static final String SLOWEST80_LINK = "slowest80Link"; + + /** + * Link for slowest 20% sqls. + */ + private static final String SLOWEST20_LINK = "slowest20Link"; + + /** + * Slowest 80/20 string. + */ + private static final String SLOWEST_80_20 = "Slowest 80%/20%:"; + + /** + * SQLs duration in invocation string. + */ + private static final String SQLS_DURATION_IN_INVOCATION = "SQLs duration in invocation:"; + + /** + * Total duration string. + */ + private static final String TOTAL_DURATION = "Total duration:"; + + /** + * Total SQLs string. + */ + private static final String TOTAL_SQLS = "Total SQLs:"; + + /** + * Reset link to be added to the slowest 80/20 count when selection is active. + */ + private static final String RESET_LINK = "[RESET]"; + + /** + * System {@link SWT#COLOR_DARK_GREEN} color. + */ + private static final RGB GREEN_RGB; + + /** + * System {@link SWT#COLOR_DARK_YELLOW} color. + */ + private static final RGB YELLOW_RGB; + + /** + * System {@link SWT#COLOR_RED} color. + */ + private static final RGB RED_RGB; + + /** + * Local resource manager for color creation. + */ + private ResourceManager resourceManager = new LocalResourceManager(JFaceResources.getResources()); + + /** + * Main composite. + */ + private Composite main; + + /** + * Total count SQL field. + */ + private FormText totalSql; + + /** + * Total SQL duration field. + */ + private FormText totalDuration; + + /** + * Percentage in invocation. + */ + private FormText percentageOfDuration; + + /** + * Slowest 80%/20% count. + */ + private FormText slowestCount; + + /** + * HYperlink settings so that we can change the link color. + */ + private HyperlinkSettings slowestHyperlinkSettings; + + /** + * List that will be passed to display slowest 80%. + */ + private Collection slowest80List = new ArrayList<>(); + + /** + * List that will be passed to display slowest 20%. + */ + private Collection slowest20List = new ArrayList<>(); + + /** + * Keep the source invocations. + */ + private List sourceInvocations; + + /** + * Content displayed in slowest 80/20 without the link. + */ + private String slowestContent; + + /** + * If reset link is displayed. + */ + private boolean resetDisplayed; + + static { + Display display = Display.getDefault(); + GREEN_RGB = display.getSystemColor(SWT.COLOR_DARK_GREEN).getRGB(); + YELLOW_RGB = display.getSystemColor(SWT.COLOR_DARK_YELLOW).getRGB(); + RED_RGB = display.getSystemColor(SWT.COLOR_RED).getRGB(); + } + + /** + * {@inheritDoc} + */ + @Override + public void createPartControl(Composite parent, FormToolkit toolkit) { + main = toolkit.createComposite(parent, SWT.BORDER); + main.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + GridLayout gl = new GridLayout(8, false); + main.setLayout(gl); + + toolkit.createLabel(main, null).setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_DATABASE)); + + totalSql = toolkit.createFormText(main, false); + totalSql.setToolTipText("Total amount of SQL Statements executed in the invocation"); + totalSql.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + toolkit.createLabel(main, null).setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_TIME)); + + totalDuration = toolkit.createFormText(main, false); + totalDuration.setToolTipText("Duration sum of all SQL Statements executed in the invocation"); + totalDuration.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + toolkit.createLabel(main, null).setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_INVOCATION)); + + percentageOfDuration = toolkit.createFormText(main, false); + percentageOfDuration.setToolTipText("Percentage of the time spent in the invocation on SQL Statements execution"); + percentageOfDuration.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + toolkit.createLabel(main, null).setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_HELP)); + + slowestCount = toolkit.createFormText(main, false); + slowestCount.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + slowestCount.setToolTipText("Amount of slowest SQL Statements that take 80%/20% time of total SQL execution duration"); + + // remove left and right margins from the parent + Layout parentLayout = parent.getLayout(); + if (parentLayout instanceof GridLayout) { + ((GridLayout) parentLayout).marginWidth = 0; + ((GridLayout) parentLayout).marginHeight = 0; + } + + setDefaultText(); + + slowestHyperlinkSettings = new HyperlinkSettings(parent.getDisplay()); + slowestHyperlinkSettings.setHyperlinkUnderlineMode(HyperlinkSettings.UNDERLINE_HOVER); + slowestCount.setHyperlinkSettings(slowestHyperlinkSettings); + slowestCount.addHyperlinkListener(getHyperlinkAdapter()); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public void setDataInput(List data) { + if (CollectionUtils.isNotEmpty(data)) { + DefaultData defaultData = data.get(0); + if (defaultData instanceof InvocationSequenceData) { + updateRepresentation((List) data); + } + } else { + setDefaultText(); + } + } + + /** + * Updates the representation of the text form. + * + * @param invocations + * Invocations to display. + */ + @SuppressWarnings("unchecked") + private void updateRepresentation(List invocations) { + sourceInvocations = invocations; + resetDisplayed = false; + + MutableDouble duration = new MutableDouble(0d); + List sqlList = new ArrayList(); + InvocationSequenceDataHelper.collectSqlsInInvocations(invocations, sqlList, duration); + double totalInvocationsDuration = 0d; + for (InvocationSequenceData inv : invocations) { + totalInvocationsDuration += inv.getDuration(); + } + double percentage = duration.toDouble() / totalInvocationsDuration * 100; + + slowest80List.clear(); + int slowest80 = getSlowestSqlCount(duration.toDouble(), sqlList, 0.8d, slowest80List); + int slowest20 = sqlList.size() - slowest80; + slowest20List = CollectionUtils.subtract(sqlList, slowest80List); + + totalSql.setText("

" + TOTAL_SQLS + " " + sqlList.size() + "

", true, false); + totalDuration.setText("

" + TOTAL_DURATION + " " + NumberFormatter.formatDouble(duration.doubleValue()) + " ms

", true, false); + + String formatedPercentage = NumberFormatter.formatDouble(percentage, 1); + if (CollectionUtils.isNotEmpty(sqlList)) { + Color durationInInvocationColor = ColorFormatter.getPerformanceColor(GREEN_RGB, YELLOW_RGB, RED_RGB, percentage, 20d, 80d, resourceManager); + percentageOfDuration.setColor("durationInInvocationColor", durationInInvocationColor); + percentageOfDuration.setText("

" + SQLS_DURATION_IN_INVOCATION + " " + formatedPercentage + "%

", true, false); + } else { + percentageOfDuration.setText("

" + SQLS_DURATION_IN_INVOCATION + " " + formatedPercentage + "%

", true, false); + } + + String slowest80String = getCountAndPercentage(slowest80, sqlList.size()); + String slowest20String = getCountAndPercentage(slowest20, sqlList.size()); + if (CollectionUtils.isNotEmpty(sqlList)) { + double slowest80Percentage = (double) slowest80 / sqlList.size() * 100; + if (Double.isNaN(slowest80Percentage)) { + slowest80Percentage = 0; + } + Color color8020 = ColorFormatter.getPerformanceColor(GREEN_RGB, YELLOW_RGB, RED_RGB, slowest80Percentage, 70d, 10d, resourceManager); + slowestCount.setColor(SLOWEST8020_COLOR, color8020); + slowestHyperlinkSettings.setForeground(color8020); + + StringBuilder text = new StringBuilder("" + SLOWEST_80_20 + " "); + if (slowest80 > 0) { + text.append("" + slowest80String + ""); + } else { + text.append("" + slowest80String + ""); + } + text.append(" / "); + if (slowest20 > 0) { + text.append("" + slowest20String + ""); + } else { + text.append("" + slowest20String + ""); + } + slowestContent = text.toString(); + } else { + slowestContent = "" + SLOWEST_80_20 + " " + slowest80String + " / " + slowest20String; + + } + slowestCount.setText("

" + slowestContent + "

", true, false); + + main.layout(); + } + + /** + * Returns the {@link HyperlinkAdapter} to handle the Hyperlink clicks. + * + * @return Returns the {@link HyperlinkAdapter} to handle the Hyperlink clicks. + */ + private HyperlinkAdapter getHyperlinkAdapter() { + return new HyperlinkAdapter() { + @Override + public void linkActivated(final HyperlinkEvent e) { + Display.getDefault().asyncExec(new Runnable() { + public void run() { + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + IWorkbenchPage page = window.getActivePage(); + IRootEditor rootEditor = (IRootEditor) page.getActiveEditor(); + if (SLOWEST80_LINK.equals(e.getHref())) { + rootEditor.setDataInput(new ArrayList<>(slowest80List)); + showResetFor8020(true); + } else if (SLOWEST20_LINK.equals(e.getHref())) { + rootEditor.setDataInput(new ArrayList<>(slowest20List)); + showResetFor8020(true); + } else { + rootEditor.setDataInput(sourceInvocations); + showResetFor8020(false); + } + } + }); + } + }; + } + + /** + * Define if reset button should be displayed in the 80/20 test. + * + * @param show + * If true reset link will be show, otherwise hidden. + */ + private void showResetFor8020(boolean show) { + if (show && !resetDisplayed) { + resetDisplayed = true; + slowestCount.setText("

" + slowestContent + " " + RESET_LINK + "

", true, false); + } else if (!show && resetDisplayed) { + resetDisplayed = false; + slowestCount.setText("

" + slowestContent + "

", true, false); + } + } + + /** + * Sets default text that has no informations displayed. + */ + private void setDefaultText() { + resetDisplayed = false; + totalSql.setText("

" + TOTAL_SQLS + "

", true, false); + totalDuration.setText("

" + TOTAL_DURATION + "

", true, false); + percentageOfDuration.setText("

" + SQLS_DURATION_IN_INVOCATION + "

", true, false); + slowestCount.setText("

" + SLOWEST_80_20 + "

", true, false); + } + + /** + * Returns string representation of count and percentage. + * + * @param count + * Count. + * @param totalCount + * Total count. + * @return {@link String} representation. + */ + private String getCountAndPercentage(int count, int totalCount) { + if (0 == totalCount) { + return "0(0%)"; + } + return count + "(" + NumberFormatter.formatDouble((double) count / totalCount * 100, 0) + "%)"; + } + + /** + * Calculates how much slowest SQL can fit into the given percentage of total duration. + * + * @param totalDuration + * Total duration of all SQLs. + * @param sqlStatementDataList + * List of SQL. Note that there is a side effect of list sorting. + * @param percentage + * Wanted percentages to be calculated. + * @param resultList + * List to add the resulting statements to. + * @return Return the count of SQL. + */ + private int getSlowestSqlCount(double totalDuration, List sqlStatementDataList, double percentage, Collection resultList) { + // sort first + Collections.sort(sqlStatementDataList, new Comparator() { + @Override + public int compare(SqlStatementData o1, SqlStatementData o2) { + return ObjectUtils.compare(o2.getDuration(), o1.getDuration()); + } + }); + + int result = 0; + double currentDurationSum = 0; + for (SqlStatementData sqlStatementData : sqlStatementDataList) { + if (currentDurationSum / totalDuration < percentage) { + result++; + resultList.add(sqlStatementData); + } else { + break; + } + currentDurationSum += sqlStatementData.getDuration(); + } + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + resourceManager.dispose(); + super.dispose(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/SqlStatementTextInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/SqlStatementTextInputController.java new file mode 100644 index 000000000..2f6121cf5 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/SqlStatementTextInputController.java @@ -0,0 +1,202 @@ +package info.novatec.inspectit.rcp.editor.text.input; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; + +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Layout; +import org.eclipse.ui.forms.widgets.FormText; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * Simple {@link TextInputController} that display the text of the SQL query with the '?' characters + * bold. + * + * @author Ivan Senic + * + */ +public class SqlStatementTextInputController extends AbstractTextInputController { + + /** + * Form text to display the data. + */ + private FormText formText; + + /** + * Main composite. + */ + private Composite main; + + /** + * {@link ScrolledComposite} that will hold the main composite. + */ + private ScrolledComposite scrollComposite; + + /** + * {@inheritDoc} + */ + @Override + public void createPartControl(Composite parent, FormToolkit toolkit) { + scrollComposite = new ScrolledComposite(parent, SWT.V_SCROLL | SWT.BORDER); + scrollComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + scrollComposite.setBackground(toolkit.getColors().getBackground()); + + main = toolkit.createComposite(scrollComposite); + GridLayout gl = new GridLayout(2, false); + gl.horizontalSpacing = 10; + main.setLayout(gl); + + Label img = toolkit.createLabel(main, null, SWT.NONE); + img.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_DATABASE)); + img.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, false, true)); + + formText = toolkit.createFormText(main, false); + formText.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, true)); + + scrollComposite.setContent(main); + scrollComposite.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + fitSizeOfScrolledContent(); + } + }); + + // remove margins from the parent + Layout parentLayout = parent.getLayout(); + if (parentLayout instanceof GridLayout) { + ((GridLayout) parentLayout).marginHeight = 0; + ((GridLayout) parentLayout).marginWidth = 0; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setDataInput(List data) { + if (null != data && !data.isEmpty()) { + DefaultData defaultData = data.get(0); + if (defaultData instanceof SqlHolderHelper) { + SqlHolderHelper sqlHolderHelper = (SqlHolderHelper) defaultData; + if (!sqlHolderHelper.isMaster() && !sqlHolderHelper.getSqlStatementDataList().isEmpty()) { + updateRepresentation(sqlHolderHelper.getSqlStatementDataList().get(0)); + return; + } + } + } + updateRepresentation(null); + } + + /** + * Updates the representation of the text form. + * + * @param dataToDisplay + * Sql to display. + */ + private void updateRepresentation(SqlStatementData dataToDisplay) { + if (null != dataToDisplay) { + String boldSql = StringUtils.replaceEach(dataToDisplay.getSql(), new String[] { "?", "<", ">", "&" }, new String[] { "?", "<", ">", "&" }); + if (CollectionUtils.isNotEmpty(dataToDisplay.getParameterValues())) { + int index = 0; + StringBuilder stringBuilder = new StringBuilder(boldSql.length()); + for (int i = 0; i < boldSql.length(); i++) { + char c = boldSql.charAt(i); + if ('?' == c) { + String parameter = dataToDisplay.getParameterValues().get(index); + if (null == parameter || "".equals(parameter.trim())) { + stringBuilder.append(c); + } else { + stringBuilder.append(parameter); + } + index = index + 1; + } else { + stringBuilder.append(c); + } + } + boldSql = stringBuilder.toString(); + } + formText.setText("

" + boldSql + "

", true, false); + } else { + formText.setText("", false, false); + } + main.layout(); + fitSizeOfScrolledContent(); + } + + /** + * Fits the width of the main composite to the same width scrolled composite was given. + */ + private void fitSizeOfScrolledContent() { + Point p = scrollComposite.getSize(); + main.setSize(main.computeSize(p.x, SWT.DEFAULT)); + } + + /** + * Helper class for passing the SQL data to the right sub-view. + * + * @author Ivan Senic + * + */ + public static class SqlHolderHelper extends DefaultData { + + /** + * Generated UID. + */ + private static final long serialVersionUID = 3529538348986684584L; + + /** + * If data is meant to be displayed in master or slave view of the SQL parameters sub-part. + */ + private final boolean master; + + /** + * Holding SQL data. + */ + private final List sqlStatementDataList; + + /** + * @param sqlStatementData + * Holding SQL data. + * @param master + * If data is meant to be displayed in master or slave view of the SQL parameters + * sub-part. + */ + public SqlHolderHelper(List sqlStatementData, boolean master) { + this.sqlStatementDataList = sqlStatementData; + this.master = master; + } + + /** + * Gets {@link #sqlStatementData}. + * + * @return {@link #sqlStatementData} + */ + public List getSqlStatementDataList() { + return sqlStatementDataList; + } + + /** + * Gets {@link #master}. + * + * @return {@link #master} + */ + public boolean isMaster() { + return master; + } + + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/TextInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/TextInputController.java new file mode 100644 index 000000000..3095f14de --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/TextInputController.java @@ -0,0 +1,61 @@ +package info.novatec.inspectit.rcp.editor.text.input; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; + +import java.util.List; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * The controller for all text inputs. + * + * @author Patrice Bouillet + * + */ +public interface TextInputController { + + /** + * Sets the input definition of this controller. + * + * @param inputDefinition + * The input definition. + */ + void setInputDefinition(InputDefinition inputDefinition); + + /** + * Returns an object containing the composite with the whole input. + * + * @param parent + * The parent used to draw the elements to. + * @param toolkit + * The form toolkit. + */ + void createPartControl(Composite parent, FormToolkit toolkit); + + /** + * The do refresh method is called at least one time to fill the labels with some initial data. + * It depends on several settings if this method is called repeatedly. + *

+ * Note that this method is not called in the UI thread because it is expected that this can + * be long running operation. Any access to the widgets in this method must be run in UI thread + * to ensure no InvalidThreadException occurs. + */ + void doRefresh(); + + /** + * Disposes this view / editor. + */ + void dispose(); + + /** + * This method is called when the input of the + * {@link info.novatec.inspectit.rcp.editor.text.TextSubView} has been changed. + * + * @param data + * New input. + */ + void setDataInput(List data); + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/ThreadsInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/ThreadsInputController.java new file mode 100644 index 000000000..5e4bd687b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/ThreadsInputController.java @@ -0,0 +1,128 @@ +package info.novatec.inspectit.rcp.editor.text.input; + +import info.novatec.inspectit.cmr.service.IGlobalDataAccessService; +import info.novatec.inspectit.communication.data.ThreadInformationData; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * This class represents the textual view of the {@link ThreadInformation} sensor-type. + * + * @author Eduard Tudenhoefner + * + */ +public class ThreadsInputController extends AbstractTextInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.text.threads"; + + /** + * The name of the section. + */ + private static final String SECTION_THREADS = "Threads"; + + /** + * The template of the {@link ThreadInformationData} object. + */ + private ThreadInformationData threadObj; + + /** + * The label for live threads. + */ + private Label liveThreadCount; + + /** + * The label for daemon threads. + */ + private Label daemonThreadCount; + + /** + * The label for total started threads. + */ + private Label totalStartedThreadCount; + + /** + * The label for peak threads. + */ + private Label peakThreadCount; + + /** + * The global data access service. + */ + private IGlobalDataAccessService dataAccessService; + + /** + * {@inheritDoc} + */ + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + threadObj = new ThreadInformationData(); + threadObj.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); + + dataAccessService = inputDefinition.getRepositoryDefinition().getGlobalDataAccessService(); + } + + /** + * {@inheritDoc} + */ + public void createPartControl(Composite parent, FormToolkit toolkit) { + addSection(parent, toolkit, SECTION_THREADS); + + if (sections.containsKey(SECTION_THREADS)) { + // creates the labels + addItemToSection(toolkit, SECTION_THREADS, "Live threads: "); + liveThreadCount = toolkit.createLabel(sections.get(SECTION_THREADS), "n/a", SWT.LEFT); + liveThreadCount.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + addItemToSection(toolkit, SECTION_THREADS, "Daemon threads: "); + daemonThreadCount = toolkit.createLabel(sections.get(SECTION_THREADS), "n/a", SWT.LEFT); + daemonThreadCount.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + addItemToSection(toolkit, SECTION_THREADS, "Peak: "); + peakThreadCount = toolkit.createLabel(sections.get(SECTION_THREADS), "n/a", SWT.LEFT); + peakThreadCount.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + addItemToSection(toolkit, SECTION_THREADS, "Total threads started: "); + totalStartedThreadCount = toolkit.createLabel(sections.get(SECTION_THREADS), "n/a", SWT.LEFT); + totalStartedThreadCount.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + } + } + + /** + * {@inheritDoc} + */ + public void doRefresh() { + final ThreadInformationData data = (ThreadInformationData) dataAccessService.getLastDataObject(threadObj); + + if (null != data) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + // updates the labels + int count = data.getCount(); + liveThreadCount.setText(NumberFormatter.formatInteger(data.getTotalThreadCount() / count)); + daemonThreadCount.setText(NumberFormatter.formatInteger(data.getTotalDaemonThreadCount() / count)); + totalStartedThreadCount.setText(NumberFormatter.formatLong(data.getTotalTotalStartedThreadCount() / count)); + peakThreadCount.setText(NumberFormatter.formatInteger(data.getTotalPeakThreadCount() / count)); + } + }); + + } + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/UngroupedExceptionOverviewStackTraceInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/UngroupedExceptionOverviewStackTraceInputController.java new file mode 100644 index 000000000..775a4c40e --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/UngroupedExceptionOverviewStackTraceInputController.java @@ -0,0 +1,58 @@ +package info.novatec.inspectit.rcp.editor.text.input; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; + +import java.util.List; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * Textual input controller for displaying the stack trace of a single {@link ExceptionSensorData} + * object. + * + * @author Ivan Senic + * + */ +public class UngroupedExceptionOverviewStackTraceInputController extends AbstractTextInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.text.ungroupedexceptionoverviewstacktrace"; + + /** + * Text box to display the stack trace. + */ + private Text stackTraceText; + + /** + * {@inheritDoc} + */ + @Override + public void createPartControl(Composite parent, FormToolkit toolkit) { + stackTraceText = toolkit.createText(parent, "", SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI); + stackTraceText.setEditable(false); + stackTraceText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + } + + /** + * {@inheritDoc} + */ + @Override + public void setDataInput(List data) { + if (data == null || data.isEmpty()) { + stackTraceText.setText(""); + } else { + Object input = data.get(0); + if (input instanceof ExceptionSensorData) { + stackTraceText.setText(((ExceptionSensorData) input).getStackTrace()); + } + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/VmSummaryInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/VmSummaryInputController.java new file mode 100644 index 000000000..fded7605e --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/text/input/VmSummaryInputController.java @@ -0,0 +1,698 @@ +package info.novatec.inspectit.rcp.editor.text.input; + +import info.novatec.inspectit.cmr.service.IGlobalDataAccessService; +import info.novatec.inspectit.communication.data.ClassLoadingInformationData; +import info.novatec.inspectit.communication.data.CompilationInformationData; +import info.novatec.inspectit.communication.data.CpuInformationData; +import info.novatec.inspectit.communication.data.MemoryInformationData; +import info.novatec.inspectit.communication.data.RuntimeInformationData; +import info.novatec.inspectit.communication.data.SystemInformationData; +import info.novatec.inspectit.communication.data.ThreadInformationData; +import info.novatec.inspectit.communication.data.VmArgumentData; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.TreeSet; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.ScrolledForm; +import org.eclipse.ui.forms.widgets.Section; + +/** + * This class represents the textual view of all platform-sensor-types. The shown informations are + * static and dynamic + * + * @author Eduard Tudenhoefner + * + */ +public class VmSummaryInputController extends AbstractTextInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.text.vmsummary"; + + /** + * The name of the vm section. + */ + private static final String SECTION_VM = "VM"; + + /** + * The name of the classes section. + */ + private static final String SECTION_CLASSES = "Classes"; + + /** + * The name of the memory section. + */ + private static final String SECTION_MEMORY = "Memory"; + + /** + * The name of the threads section. + */ + private static final String SECTION_THREADS = "Threads"; + + /** + * The name of the operating system section. + */ + private static final String SECTION_OS = "Operating System"; + + /** + * The name of the classpath section. + */ + private static final String SECTION_CLASSPATH = "Class Path"; + + /** + * The name of the vm arguments section. + */ + private static final String VM_ARGS = "VM Arguments"; + + /** + * The string representing that something is not available. + */ + private static final String NOT_AVAILABLE = "N/A"; + + /** + * The form containing all labels and sections. + */ + private ScrolledForm scrolledForm; + + /** + * The template of the {@link SystemInformationData} object. + */ + private SystemInformationData systemObj; + + /** + * The template of the {@link RuntimeInformationData} object. + */ + private RuntimeInformationData runtimeObj; + + /** + * The template of the {@link MemoryInformationData} object. + */ + private MemoryInformationData memoryObj; + + /** + * The template of the {@link ClassLoadingInformationData} object. + */ + private ClassLoadingInformationData classLoadingObj; + + /** + * The template of the {@link CpuInformationData} object. + */ + private CpuInformationData cpuObj; + + /** + * The template of the {@link CompilationInformationData} object. + */ + private CompilationInformationData compilationObj; + + /** + * The template of the {@link ThreadInformationData} object. + */ + private ThreadInformationData threadObj; + + /** + * The {@link HashMap} containing the minimized sections. + */ + private Map minimizedSections = new HashMap(); + + /** + * The label for loaded classes. + */ + private Label loadedClassCount; + + /** + * The label for total loaded classes. + */ + private Label totalLoadedClassCount; + + /** + * The label for unloaded classes. + */ + private Label unloadedClassCount; + + /** + * The label for the uptime of the virtual machine. + */ + private Label uptime; + + /** + * The label for the process cpu time. + */ + private Label processCpuTime; + + /** + * The label for live threads. + */ + private Label liveThreadCount; + + /** + * The label for daemon threads. + */ + private Label daemonThreadCount; + + /** + * The label for total started threads. + */ + private Label totalStartedThreadCount; + + /** + * The label for peak threads. + */ + private Label peakThreadCount; + + /** + * The label for total compilation time. + */ + private Label totalCompilationTime; + + /** + * The label for free physical memory. + */ + private Label freePhysMemory; + + /** + * The label for free swap space. + */ + private Label freeSwapSpace; + + /** + * The label for committed heap memory size. + */ + private Label committedHeapMemorySize; + + /** + * The label for committed non-heap memory size. + */ + private Label committedNonHeapMemorySize; + + /** + * The label for used heap memory size. + */ + private Label usedHeapMemorySize; + + /** + * The label for used non-heap memory size. + */ + private Label usedNonHeapMemorySize; + + /** + * The global data access service. + */ + private IGlobalDataAccessService dataAccessService; + + /** + * {@inheritDoc} + */ + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + long platformId = inputDefinition.getIdDefinition().getPlatformId(); + + systemObj = new SystemInformationData(); + systemObj.setPlatformIdent(platformId); + + classLoadingObj = new ClassLoadingInformationData(); + classLoadingObj.setPlatformIdent(platformId); + + cpuObj = new CpuInformationData(); + cpuObj.setPlatformIdent(platformId); + + compilationObj = new CompilationInformationData(); + compilationObj.setPlatformIdent(platformId); + + memoryObj = new MemoryInformationData(); + memoryObj.setPlatformIdent(platformId); + + runtimeObj = new RuntimeInformationData(); + runtimeObj.setPlatformIdent(platformId); + + threadObj = new ThreadInformationData(); + threadObj.setPlatformIdent(platformId); + + dataAccessService = inputDefinition.getRepositoryDefinition().getGlobalDataAccessService(); + } + + /** + * {@inheritDoc} + */ + public void createPartControl(Composite parent, FormToolkit toolkit) { + scrolledForm = toolkit.createScrolledForm(parent); + scrolledForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + scrolledForm.getBody().setLayout(new GridLayout(1, true)); + scrolledForm.getBody().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + this.initializeInput(scrolledForm.getBody(), toolkit); + } + + /** + * Returns the new composite with initialized input and sections with text labels. + * + * @param parent + * The parent used to draw the elements to. + * @param toolkit + * The form toolkit. + * + */ + private void initializeInput(Composite parent, FormToolkit toolkit) { + int labelStyle = SWT.LEFT; + int minTitleColumnWidth = 170; + int minInformationColumnWidth = 230; + + // add sections + addSection(parent, toolkit, SECTION_VM); + addSection(parent, toolkit, SECTION_OS); + addSection(parent, toolkit, SECTION_MEMORY); + addSection(parent, toolkit, SECTION_CLASSES); + addSection(parent, toolkit, SECTION_THREADS); + + // static text informations + addStaticInformations(parent, toolkit); + + // dynamic text informations + if (sections.containsKey(SECTION_CLASSES)) { + // creates the labels for the 'classes' section + addItemToSection(toolkit, SECTION_CLASSES, "Current loaded classes: ", minTitleColumnWidth); + loadedClassCount = toolkit.createLabel(sections.get(SECTION_CLASSES), NOT_AVAILABLE, labelStyle); + loadedClassCount.setLayoutData(new GridData(minInformationColumnWidth, SWT.DEFAULT)); + + addItemToSection(toolkit, SECTION_CLASSES, "Total loaded classes: ", minTitleColumnWidth); + totalLoadedClassCount = toolkit.createLabel(sections.get(SECTION_CLASSES), NOT_AVAILABLE, labelStyle); + totalLoadedClassCount.setLayoutData(new GridData(minInformationColumnWidth, SWT.DEFAULT)); + + addItemToSection(toolkit, SECTION_CLASSES, "Total unloaded classes: ", minTitleColumnWidth); + unloadedClassCount = toolkit.createLabel(sections.get(SECTION_CLASSES), NOT_AVAILABLE, labelStyle); + unloadedClassCount.setLayoutData(new GridData(minInformationColumnWidth, SWT.DEFAULT)); + } + + if (sections.containsKey(SECTION_VM)) { + // creates the labels for the 'vm' section + addItemToSection(toolkit, SECTION_VM, "Total compile time: ", minTitleColumnWidth); + totalCompilationTime = toolkit.createLabel(sections.get(SECTION_VM), NOT_AVAILABLE, labelStyle); + totalCompilationTime.setLayoutData(new GridData(minInformationColumnWidth, SWT.DEFAULT)); + + addItemToSection(toolkit, SECTION_VM, "Uptime: ", minTitleColumnWidth); + uptime = toolkit.createLabel(sections.get(SECTION_VM), NOT_AVAILABLE, labelStyle); + uptime.setLayoutData(new GridData(minInformationColumnWidth, SWT.DEFAULT)); + + addItemToSection(toolkit, SECTION_VM, "Process Cpu Time: ", minTitleColumnWidth); + processCpuTime = toolkit.createLabel(sections.get(SECTION_VM), NOT_AVAILABLE, labelStyle); + processCpuTime.setLayoutData(new GridData(minInformationColumnWidth, SWT.DEFAULT)); + } + + if (sections.containsKey(SECTION_MEMORY)) { + // creates the labels for the 'memory' section + addItemToSection(toolkit, SECTION_MEMORY, "Free physical memory: ", minTitleColumnWidth); + freePhysMemory = toolkit.createLabel(sections.get(SECTION_MEMORY), NOT_AVAILABLE, labelStyle); + freePhysMemory.setLayoutData(new GridData(minInformationColumnWidth, SWT.DEFAULT)); + + addItemToSection(toolkit, SECTION_MEMORY, "Free swap space: ", minTitleColumnWidth); + freeSwapSpace = toolkit.createLabel(sections.get(SECTION_MEMORY), NOT_AVAILABLE, labelStyle); + freeSwapSpace.setLayoutData(new GridData(minInformationColumnWidth, SWT.DEFAULT)); + + addItemToSection(toolkit, SECTION_MEMORY, "Committed heap size: ", minTitleColumnWidth); + committedHeapMemorySize = toolkit.createLabel(sections.get(SECTION_MEMORY), NOT_AVAILABLE, labelStyle); + committedHeapMemorySize.setLayoutData(new GridData(minInformationColumnWidth, SWT.DEFAULT)); + + addItemToSection(toolkit, SECTION_MEMORY, "Committed non-heap size: ", minTitleColumnWidth); + committedNonHeapMemorySize = toolkit.createLabel(sections.get(SECTION_MEMORY), NOT_AVAILABLE, labelStyle); + committedNonHeapMemorySize.setLayoutData(new GridData(minInformationColumnWidth, SWT.DEFAULT)); + + addItemToSection(toolkit, SECTION_MEMORY, "Used heap size: ", minTitleColumnWidth); + usedHeapMemorySize = toolkit.createLabel(sections.get(SECTION_MEMORY), NOT_AVAILABLE, labelStyle); + usedHeapMemorySize.setLayoutData(new GridData(minInformationColumnWidth, SWT.DEFAULT)); + + addItemToSection(toolkit, SECTION_MEMORY, "Used non-heap size: ", minTitleColumnWidth); + usedNonHeapMemorySize = toolkit.createLabel(sections.get(SECTION_MEMORY), NOT_AVAILABLE, labelStyle); + usedNonHeapMemorySize.setLayoutData(new GridData(minInformationColumnWidth, SWT.DEFAULT)); + } + + if (sections.containsKey(SECTION_THREADS)) { + // creates the labels for the 'threads' section + addItemToSection(toolkit, SECTION_THREADS, "Live threads: ", minTitleColumnWidth); + liveThreadCount = toolkit.createLabel(sections.get(SECTION_THREADS), NOT_AVAILABLE, labelStyle); + liveThreadCount.setLayoutData(new GridData(minInformationColumnWidth, SWT.DEFAULT)); + + addItemToSection(toolkit, SECTION_THREADS, "Daemon threads: ", minTitleColumnWidth); + daemonThreadCount = toolkit.createLabel(sections.get(SECTION_THREADS), NOT_AVAILABLE, labelStyle); + daemonThreadCount.setLayoutData(new GridData(minInformationColumnWidth, SWT.DEFAULT)); + + addItemToSection(toolkit, SECTION_THREADS, "Peak: ", minTitleColumnWidth); + peakThreadCount = toolkit.createLabel(sections.get(SECTION_THREADS), NOT_AVAILABLE, labelStyle); + peakThreadCount.setLayoutData(new GridData(minInformationColumnWidth, SWT.DEFAULT)); + + addItemToSection(toolkit, SECTION_THREADS, "Total threads started: ", minTitleColumnWidth); + totalStartedThreadCount = toolkit.createLabel(sections.get(SECTION_THREADS), NOT_AVAILABLE, labelStyle); + totalStartedThreadCount.setLayoutData(new GridData(minInformationColumnWidth, SWT.DEFAULT)); + } + } + + /** + * Adds some static text informations to the parent component. + * + * @param parent + * The parent used to draw the elements to. + * @param toolkit + * The form toolkit. + */ + private void addStaticInformations(Composite parent, FormToolkit toolkit) { + SystemInformationData data = (SystemInformationData) dataAccessService.getLastDataObject(systemObj); + int minTitleColumnWidth = 170; + int minInformationColumnWidth = 230; + + String processId = NOT_AVAILABLE; + String pcName = NOT_AVAILABLE; + + if (null != data) { + // add the panels + addMinimizedSection(parent, toolkit, SECTION_CLASSPATH, 1); + addMinimizedSection(parent, toolkit, VM_ARGS, 2); + + // split vm name + String vmFullName = data.getVmName(); + String[] vmNames = vmFullName.split("@"); + if (vmNames != null && vmNames.length > 1) { + processId = vmNames[0]; + pcName = vmNames[1]; + } + + addItemToSection(toolkit, SECTION_VM, "Vendor: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_VM, data.getVmVendor(), minInformationColumnWidth); + addItemToSection(toolkit, SECTION_VM, "Version: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_VM, data.getVmVersion(), minInformationColumnWidth); + addItemToSection(toolkit, SECTION_VM, "Process Id: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_VM, processId, minInformationColumnWidth); + addItemToSection(toolkit, SECTION_VM, "Pc Name: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_VM, pcName, minInformationColumnWidth); + addItemToSection(toolkit, SECTION_VM, "Jit Compiler Name: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_VM, data.getJitCompilerName(), minInformationColumnWidth); + addItemToSection(toolkit, SECTION_VM, "Specification Name: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_VM, data.getVmSpecName(), minInformationColumnWidth); + addItemToSection(toolkit, SECTION_MEMORY, "Max heap size: ", minTitleColumnWidth); + if (data.getMaxHeapMemorySize() > 0) { + addItemToSection(toolkit, SECTION_MEMORY, NumberFormatter.formatBytesToKBytes(data.getMaxHeapMemorySize()), minInformationColumnWidth); + } else { + addItemToSection(toolkit, SECTION_MEMORY, NOT_AVAILABLE, minInformationColumnWidth); + } + addItemToSection(toolkit, SECTION_MEMORY, "Max non-heap size: ", minTitleColumnWidth); + if (data.getMaxNonHeapMemorySize() > 0) { + addItemToSection(toolkit, SECTION_MEMORY, NumberFormatter.formatBytesToKBytes(data.getMaxNonHeapMemorySize()), minInformationColumnWidth); + } else { + addItemToSection(toolkit, SECTION_MEMORY, NOT_AVAILABLE, minInformationColumnWidth); + } + addItemToSection(toolkit, SECTION_MEMORY, "Total physical memory: ", minTitleColumnWidth); + if (data.getTotalPhysMemory() > 0) { + addItemToSection(toolkit, SECTION_MEMORY, NumberFormatter.formatBytesToKBytes(data.getTotalPhysMemory()), minInformationColumnWidth); + } else { + addItemToSection(toolkit, SECTION_MEMORY, NOT_AVAILABLE, minInformationColumnWidth); + } + addItemToSection(toolkit, SECTION_MEMORY, "Total swap space: ", minTitleColumnWidth); + if (data.getTotalSwapSpace() > 0) { + addItemToSection(toolkit, SECTION_MEMORY, NumberFormatter.formatBytesToKBytes(data.getTotalSwapSpace()), minInformationColumnWidth); + } else { + addItemToSection(toolkit, SECTION_MEMORY, NOT_AVAILABLE, minInformationColumnWidth); + } + addItemToSection(toolkit, SECTION_OS, "Operating System: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_OS, data.getOsName() + " " + data.getOsVersion(), minInformationColumnWidth); + addItemToSection(toolkit, SECTION_OS, "Available processors: ", minTitleColumnWidth); + if (data.getAvailableProcessors() > 0) { + addItemToSection(toolkit, SECTION_OS, NumberFormatter.formatInteger(data.getAvailableProcessors()), minInformationColumnWidth); + } else { + addItemToSection(toolkit, SECTION_OS, NOT_AVAILABLE, minInformationColumnWidth); + } + addItemToSection(toolkit, SECTION_OS, "Architecture: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_OS, data.getArchitecture(), minInformationColumnWidth); + + // token delimiter can be : or ; + // thus checking the provided class-path to see which one fits + String tokenDelimiter = ";"; + String classPath = data.getClassPath(); + if (classPath.indexOf(tokenDelimiter) == -1) { + tokenDelimiter = ":"; + } + + // some classpath informations with formatting + addItemToMinimizedSection(toolkit, SECTION_CLASSPATH, "Class path: "); + StringTokenizer classpathTokenizer = new StringTokenizer(data.getClassPath(), tokenDelimiter); + while (classpathTokenizer.hasMoreTokens()) { + addItemToMinimizedSection(toolkit, SECTION_CLASSPATH, " \t" + classpathTokenizer.nextToken()); + } + + addItemToMinimizedSection(toolkit, SECTION_CLASSPATH, "Boot class path: "); + StringTokenizer bootClasspathTokenizer = new StringTokenizer(data.getBootClassPath(), tokenDelimiter); + while (bootClasspathTokenizer.hasMoreTokens()) { + addItemToMinimizedSection(toolkit, SECTION_CLASSPATH, " \t" + bootClasspathTokenizer.nextToken()); + } + + addItemToMinimizedSection(toolkit, SECTION_CLASSPATH, "Library Path: "); + StringTokenizer libPathTokenizer = new StringTokenizer(data.getLibraryPath(), tokenDelimiter); + while (libPathTokenizer.hasMoreTokens()) { + addItemToMinimizedSection(toolkit, SECTION_CLASSPATH, " \t" + libPathTokenizer.nextToken()); + } + + // sorting vm arguments + TreeSet treeSet = new TreeSet(new Comparator() { + public int compare(VmArgumentData one, VmArgumentData two) { + return one.getVmName().compareTo(two.getVmName()); + } + }); + treeSet.addAll(data.getVmSet()); + + // showing vm arguments + for (VmArgumentData argumentData : treeSet) { + if (!argumentData.getVmName().endsWith("path") && !argumentData.getVmName().endsWith("separator")) { + addItemToMinimizedSection(toolkit, VM_ARGS, argumentData.getVmName() + ":"); + addItemToMinimizedSection(toolkit, VM_ARGS, argumentData.getVmValue()); + } + } + } else { + // if 'not available' then create the labels + addMinimizedSectionNotAvailable(parent, toolkit, SECTION_CLASSPATH + " (n/a)", 1); + addMinimizedSectionNotAvailable(parent, toolkit, VM_ARGS + " (n/a)", 2); + + addItemToSection(toolkit, SECTION_VM, "Vendor: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_VM, NOT_AVAILABLE, minInformationColumnWidth); + addItemToSection(toolkit, SECTION_VM, "Version: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_VM, NOT_AVAILABLE, minInformationColumnWidth); + addItemToSection(toolkit, SECTION_VM, "Process Id: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_VM, processId, minInformationColumnWidth); + addItemToSection(toolkit, SECTION_VM, "Pc Name: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_VM, pcName, minInformationColumnWidth); + addItemToSection(toolkit, SECTION_VM, "Jit Compiler Name: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_VM, NOT_AVAILABLE, minInformationColumnWidth); + addItemToSection(toolkit, SECTION_VM, "Specification Name: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_VM, NOT_AVAILABLE, minInformationColumnWidth); + addItemToSection(toolkit, SECTION_MEMORY, "Max heap size: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_MEMORY, NOT_AVAILABLE, minInformationColumnWidth); + addItemToSection(toolkit, SECTION_MEMORY, "Max non-heap size: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_MEMORY, NOT_AVAILABLE, minInformationColumnWidth); + addItemToSection(toolkit, SECTION_MEMORY, "Total physical memory: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_MEMORY, NOT_AVAILABLE, minInformationColumnWidth); + addItemToSection(toolkit, SECTION_MEMORY, "Total swap space: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_MEMORY, NOT_AVAILABLE, minInformationColumnWidth); + addItemToSection(toolkit, SECTION_OS, "Operating System: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_OS, NOT_AVAILABLE, minInformationColumnWidth); + addItemToSection(toolkit, SECTION_OS, "Available processors: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_OS, NOT_AVAILABLE, minInformationColumnWidth); + addItemToSection(toolkit, SECTION_OS, "Architecture: ", minTitleColumnWidth); + addItemToSection(toolkit, SECTION_OS, NOT_AVAILABLE, minInformationColumnWidth); + } + } + + /** + * Adds minimized section to bundle some content. + * + * @param parent + * The parent used to draw the elements to. + * @param toolkit + * The form toolkit. + * @param sectionTitle + * The title of the section. + * @param numColums + * The number of columns to span. + */ + private void addMinimizedSection(Composite parent, FormToolkit toolkit, String sectionTitle, int numColums) { + Section section = toolkit.createSection(parent, Section.TITLE_BAR | Section.TWISTIE); + section.setText(sectionTitle); + section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + Composite sectionComposite = toolkit.createComposite(section); + GridLayout gridLayout = new GridLayout(numColums, false); + gridLayout.marginLeft = 5; + gridLayout.marginTop = 5; + sectionComposite.setLayout(gridLayout); + section.setClient(sectionComposite); + + if (!minimizedSections.containsKey(sectionTitle)) { + minimizedSections.put(sectionTitle, sectionComposite); + } + } + + /** + * Adds a section which is not available due to not activated platform sensor types. + * + * @param parent + * The parent used to draw the elements to. + * @param toolkit + * The form toolkit. + * @param sectionTitle + * The title of the section. + * @param numColums + * the number of columns to span. + */ + private void addMinimizedSectionNotAvailable(Composite parent, FormToolkit toolkit, String sectionTitle, int numColums) { + Section section = toolkit.createSection(parent, Section.TITLE_BAR | Section.TWISTIE); + section.setText(sectionTitle); + section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + Composite sectionComposite = toolkit.createComposite(section); + GridLayout gridLayout = new GridLayout(numColums, false); + gridLayout.marginLeft = 5; + gridLayout.marginTop = 5; + sectionComposite.setLayout(gridLayout); + + section.setClient(sectionComposite); + + if (!minimizedSections.containsKey(sectionTitle)) { + minimizedSections.put(sectionTitle, sectionComposite); + } + } + + /** + * Adds an item to the specified section. + * + * @param toolkit + * The form toolkit. + * @param sectionTitle + * The title of the section. + * @param text + * The text which will be shown. + */ + private void addItemToMinimizedSection(FormToolkit toolkit, String sectionTitle, String text) { + if (minimizedSections.containsKey(sectionTitle)) { + Label label = toolkit.createLabel(minimizedSections.get(sectionTitle), text, SWT.LEFT); + label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + } + } + + /** + * {@inheritDoc} + */ + public void doRefresh() { + ClassLoadingInformationData classLoadingData = (ClassLoadingInformationData) dataAccessService.getLastDataObject(classLoadingObj); + CpuInformationData cpuData = (CpuInformationData) dataAccessService.getLastDataObject(cpuObj); + CompilationInformationData compilationData = (CompilationInformationData) dataAccessService.getLastDataObject(compilationObj); + MemoryInformationData memoryData = (MemoryInformationData) dataAccessService.getLastDataObject(memoryObj); + RuntimeInformationData runtimeData = (RuntimeInformationData) dataAccessService.getLastDataObject(runtimeObj); + ThreadInformationData threadData = (ThreadInformationData) dataAccessService.getLastDataObject(threadObj); + + updateLabels(classLoadingData, cpuData, compilationData, memoryData, runtimeData, threadData); + } + + /** + * Updates the labels with the dynamic informations. + * + * @param classLoadingData + * The {@link ClassLoadingInformationData} object. + * @param cpuData + * The {@link CpuInformationData} object. + * @param compilationData + * The {@link CompilationInformationData} object. + * @param memoryData + * The {@link MemoryInformationData} object. + * @param runtimeData + * The {@link RuntimeInformationData} object. + * @param threadData + * The {@link ThreadInformationData} object. + */ + private void updateLabels(final ClassLoadingInformationData classLoadingData, final CpuInformationData cpuData, final CompilationInformationData compilationData, + final MemoryInformationData memoryData, final RuntimeInformationData runtimeData, final ThreadInformationData threadData) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + if (classLoadingData != null) { + int count = classLoadingData.getCount(); + loadedClassCount.setText(NumberFormatter.formatInteger(classLoadingData.getTotalLoadedClassCount() / count)); + totalLoadedClassCount.setText(NumberFormatter.formatLong(classLoadingData.getTotalTotalLoadedClassCount() / count)); + unloadedClassCount.setText(NumberFormatter.formatLong(classLoadingData.getTotalUnloadedClassCount() / count)); + } + + if (cpuData != null) { + if (cpuData.getProcessCpuTime() > 0) { + processCpuTime.setText(NumberFormatter.formatNanosToSeconds(cpuData.getProcessCpuTime())); + } else { + processCpuTime.setText(NOT_AVAILABLE); + } + } + + if (compilationData != null) { + totalCompilationTime.setText(NumberFormatter.formatMillisToSeconds(compilationData.getTotalTotalCompilationTime() / compilationData.getCount())); + } + + if (memoryData != null) { + int count = memoryData.getCount(); + if (memoryData.getTotalFreePhysMemory() > 0) { + freePhysMemory.setText(NumberFormatter.formatBytesToKBytes(memoryData.getTotalFreePhysMemory() / count)); + } else { + freePhysMemory.setText(NOT_AVAILABLE); + } + if (memoryData.getTotalFreeSwapSpace() > 0) { + freeSwapSpace.setText(NumberFormatter.formatBytesToKBytes(memoryData.getTotalFreeSwapSpace() / count)); + } else { + freeSwapSpace.setText(NOT_AVAILABLE); + } + if (memoryData.getTotalComittedHeapMemorySize() > 0) { + committedHeapMemorySize.setText(NumberFormatter.formatBytesToKBytes(memoryData.getTotalComittedHeapMemorySize() / count)); + } else { + committedHeapMemorySize.setText(NOT_AVAILABLE); + } + if (memoryData.getTotalComittedNonHeapMemorySize() > 0) { + committedNonHeapMemorySize.setText(NumberFormatter.formatBytesToKBytes(memoryData.getTotalComittedNonHeapMemorySize() / count)); + } else { + committedNonHeapMemorySize.setText(NOT_AVAILABLE); + } + if (memoryData.getTotalUsedHeapMemorySize() > 0) { + usedHeapMemorySize.setText(NumberFormatter.formatBytesToKBytes(memoryData.getTotalUsedHeapMemorySize() / count)); + } else { + usedHeapMemorySize.setText(NOT_AVAILABLE); + } + if (memoryData.getTotalUsedNonHeapMemorySize() > 0) { + usedNonHeapMemorySize.setText(NumberFormatter.formatBytesToKBytes(memoryData.getTotalUsedNonHeapMemorySize() / count)); + } else { + usedNonHeapMemorySize.setText(NOT_AVAILABLE); + } + } + + if (runtimeData != null) { + uptime.setText(NumberFormatter.millisecondsToString(runtimeData.getTotalUptime() / runtimeData.getCount())); + } + + if (threadData != null) { + int count = threadData.getCount(); + liveThreadCount.setText(NumberFormatter.formatInteger(threadData.getTotalThreadCount() / count)); + daemonThreadCount.setText(NumberFormatter.formatInteger(threadData.getTotalDaemonThreadCount() / count)); + peakThreadCount.setText(NumberFormatter.formatInteger(threadData.getTotalPeakThreadCount() / count)); + totalStartedThreadCount.setText(NumberFormatter.formatLong(threadData.getTotalTotalStartedThreadCount() / count)); + } + } + }); + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tooltip/ColumnAwareToolTipSupport.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tooltip/ColumnAwareToolTipSupport.java new file mode 100644 index 000000000..9f4c6480c --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tooltip/ColumnAwareToolTipSupport.java @@ -0,0 +1,134 @@ +package info.novatec.inspectit.rcp.editor.tooltip; + +import org.eclipse.jface.util.Policy; +import org.eclipse.jface.viewers.CellLabelProvider; +import org.eclipse.jface.viewers.ColumnViewer; +import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; +import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.jface.window.ToolTip; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Event; + +/** + * Small extension of the {@link ColumnAwareToolTipSupport}. This one will check if the label + * provider of the cell is {@link IColumnToolTipProvider} and if so if will pull the tip and image + * from that interface and not directly from the label provider. + * + * @author Ivan Senic + * + */ +public class ColumnAwareToolTipSupport extends ColumnViewerToolTipSupport { + + /** + * Viewer cell key. Copied from super class. + */ + private static final String VIEWER_CELL_KEY = Policy.JFACE + "_VIEWER_CELL_KEY"; + + /** + * Shift X. Copied from super class. + */ + private static final int DEFAULT_SHIFT_X = 10; + + /** + * Shift Y. Copied from super class. + */ + private static final int DEFAULT_SHIFT_Y = 0; + + /** + * {@link ColumnViewer}. + */ + private ColumnViewer viewer; + + /** + * @param viewer + * the viewer the support is attached to + * @param style + * style passed to control tool tip behavior + * + * @param manualActivation + * true if the activation is done manually using {@link #show(Point)} + * @See {@link ColumnAwareToolTipSupport#ColumnAwareToolTipSupport(ColumnViewer, int, boolean)} + */ + public ColumnAwareToolTipSupport(ColumnViewer viewer, int style, boolean manualActivation) { + super(viewer, style, manualActivation); + this.viewer = viewer; + } + + /** + * Enable ToolTip support for the viewer by creating an instance from this class. To get all + * necessary informations this support class consults the {@link CellLabelProvider}. + * + * @param viewer + * the viewer the support is attached to + */ + public static void enableFor(ColumnViewer viewer) { + new ColumnAwareToolTipSupport(viewer, ToolTip.NO_RECREATE, false); + } + + /** + * Enable ToolTip support for the viewer by creating an instance from this class. To get all + * necessary informations this support class consults the {@link CellLabelProvider}. + * + * @param viewer + * the viewer the support is attached to + * @param style + * style passed to control tool tip behavior + * + * @see ToolTip#RECREATE + * @see ToolTip#NO_RECREATE + */ + public static void enableFor(ColumnViewer viewer, int style) { + new ColumnAwareToolTipSupport(viewer, style, false); + } + + /** + * {@inheritDoc} + */ + @Override + protected boolean shouldCreateToolTip(Event event) { + Point point = new Point(event.x, event.y); + ViewerCell cell = viewer.getCell(point); + if (cell == null) { + return false; + } + CellLabelProvider labelProvider = viewer.getLabelProvider(cell.getColumnIndex()); + if (labelProvider instanceof IColumnToolTipProvider) { + IColumnToolTipProvider columnToolTipProvider = (IColumnToolTipProvider) labelProvider; + Object element = cell.getViewerRow().getItem().getData(); + + String text = columnToolTipProvider.getToolTipText(element, cell.getColumnIndex()); + Image image = columnToolTipProvider.getToolTipImage(element, cell.getColumnIndex()); + + if (null == text && null == image) { + return false; + } + + viewer.getControl().setToolTipText(""); + + setPopupDelay(labelProvider.getToolTipDisplayDelayTime(element)); + setHideDelay(labelProvider.getToolTipTimeDisplayed(element)); + + Point shift = labelProvider.getToolTipShift(element); + + if (shift == null) { + setShift(new Point(DEFAULT_SHIFT_X, DEFAULT_SHIFT_Y)); + } else { + setShift(new Point(shift.x, shift.y)); + } + + setData(VIEWER_CELL_KEY, cell); + + setText(text); + setImage(image); + setStyle(labelProvider.getToolTipStyle(element)); + setForegroundColor(labelProvider.getToolTipForegroundColor(element)); + setBackgroundColor(labelProvider.getToolTipBackgroundColor(element)); + setFont(labelProvider.getToolTipFont(element)); + + return true; + } else { + return super.shouldCreateToolTip(event); + } + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tooltip/IColumnToolTipProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tooltip/IColumnToolTipProvider.java new file mode 100644 index 000000000..f4553a595 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tooltip/IColumnToolTipProvider.java @@ -0,0 +1,35 @@ +package info.novatec.inspectit.rcp.editor.tooltip; + +import org.eclipse.swt.graphics.Image; + +/** + * Special interface for simple tooltip providing based on the table/tree column. + * + * @author Ivan Senic + * + */ +public interface IColumnToolTipProvider { + + /** + * Returns tool-tip text. + * + * @param element + * Element to return the tool-tip for. + * @param index + * Column index. + * @return Text or null. + */ + String getToolTipText(Object element, int index); + + /** + * Returns tool-tip image. + * + * @param element + * Element to return the tool-tip for. + * @param index + * Column index. + * @return Image or null. + */ + Image getToolTipImage(Object element, int index); + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/DeferredTreeViewer.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/DeferredTreeViewer.java new file mode 100644 index 000000000..1d9ed1154 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/DeferredTreeViewer.java @@ -0,0 +1,378 @@ +package info.novatec.inspectit.rcp.editor.tree; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.commons.lang.ArrayUtils; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Item; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; +import org.eclipse.swt.widgets.Widget; +import org.eclipse.ui.progress.PendingUpdateAdapter; + +/** + * This tree viewer works in conjunction with the + * {@link org.eclipse.ui.progress.DeferredTreeContentManager} so that the expand function will work. + *

+ * IMPORTANT: The class is licensed under the Eclipse Public License v1.0 as it includes the + * code from the {@link org.eclipse.jface.viewers.TreeViewer} class belonging to the Eclipse Rich + * Client Platform. EPL v1.0 license can be found here. + *

+ * Please relate to the LICENSEEXCEPTIONS.txt file for more information about license exceptions + * that apply regarding to InspectIT and Eclipse RCP and/or EPL Components. + * + * @author Patrice Bouillet + * + */ +public class DeferredTreeViewer extends TreeViewer { + + /** + * Maps the parent widgets to the level so that we know how deep we want to go. + */ + private Map parentWidgets = Collections.synchronizedMap(new HashMap()); + + /** + * List of the elements that need to be expanded. + */ + private Set objectsToBeExpanded = Collections.synchronizedSet(new HashSet()); + + /** + * Object to be selected. + */ + private AtomicReference objectToSelect = new AtomicReference(); + + /** + * Creates a tree viewer on a newly-created tree control under the given parent. The tree + * control is created using the SWT style bits MULTI, H_SCROLL, V_SCROLL, and + * BORDER. The viewer has no input, no content provider, a default label provider, + * no sorter, and no filters. + * + * @param parent + * the parent control + */ + public DeferredTreeViewer(Composite parent) { + super(parent); + } + + /** + * Creates a tree viewer on the given tree control. The viewer has no input, no content + * provider, a default label provider, no sorter, and no filters. + * + * @param tree + * the tree control + */ + public DeferredTreeViewer(Tree tree) { + super(tree); + } + + /** + * Creates a tree viewer on a newly-created tree control under the given parent. The tree + * control is created using the given SWT style bits. The viewer has no input, no content + * provider, a default label provider, no sorter, and no filters. + * + * @param parent + * the parent control + * @param style + * the SWT style bits used to create the tree. + */ + public DeferredTreeViewer(Composite parent, int style) { + super(parent, style); + } + + /** + * {@inheritDoc} + */ + @Override + protected void internalAdd(Widget widget, Object parentElement, Object[] childElements) { + // we have to activate our own filters first, stupid eclipse + // implementation which has got two different paths of applying filters + // ... + ViewerFilter[] filters = getFilters(); + for (int i = 0; i < filters.length; i++) { + ViewerFilter filter = filters[i]; + childElements = filter.filter(this, parentElement, childElements); + } + + super.internalAdd(widget, parentElement, childElements); + + // check if we are currently in the process of expanding the child + // elements + if (parentWidgets.containsKey(widget)) { + // iterate over all child elements + for (Object object : childElements) { + // is it expandable + if (super.isExpandable(object)) { + // get the level + Integer level = parentWidgets.get(widget); + if (level == TreeViewer.ALL_LEVELS) { + super.expandToLevel(object, TreeViewer.ALL_LEVELS); + } else { + super.expandToLevel(object, level - 1); + } + } + } + } + + if (objectsToBeExpanded != null && !objectsToBeExpanded.isEmpty()) { + // iterate over all child elements + for (Object object : childElements) { + // is object in List of objects that need to be expanded? + if (objectsToBeExpanded.contains(object)) { + // then expand it + if (!getExpandedState(object)) { + super.expandToLevel(object, 1); + } + } + } + } + + // if there is object to be selected, we will selected if its parent is expanded + while (true) { + Object objToSelect = objectToSelect.get(); + if (objToSelect != null && (!isRootElement(objToSelect) || getExpandedState(getParentElement(objToSelect)))) { + List selectionList = new ArrayList(); + Widget w = internalGetWidgetToSelect(objToSelect); + if (w != null) { + if (objectToSelect.compareAndSet(objToSelect, null)) { + selectionList.add(w); + setSelection(selectionList); + break; + } + } else { + break; + } + } else { + break; + } + } + + } + + /** + * {@inheritDoc} + */ + @Override + protected void internalExpandToLevel(Widget widget, int level) { + if (level > 1 || TreeViewer.ALL_LEVELS == level) { + // we want to open more than one level, have to take care of that. + Object data = widget.getData(); + if (!(data instanceof PendingUpdateAdapter)) { + // just care about our own widgets + parentWidgets.put(widget, Integer.valueOf(level)); + } + } + + // when the widget is actually expanding, we have to remove its data from the list of object + // that + // needs to be expanded, if the data of the widget is found in the list + Object data = widget.getData(); + if (data != null && objectsToBeExpanded.contains(data)) { + objectsToBeExpanded.remove(data); + } + + super.internalExpandToLevel(widget, level); + } + + /** + * {@inheritDoc} + */ + @Override + protected void internalRemove(Object[] elementsOrPaths) { + // we want to remove the parent of the PendingUpdateAdapter items from + // our Map + if (1 == elementsOrPaths.length) { + Object object = elementsOrPaths[0]; + if (object instanceof PendingUpdateAdapter) { + Widget[] widgets = findItems(object); + if (null != widgets && widgets.length > 0) { + Widget widget = widgets[0]; + Widget parentWidget = getParentItem((Item) widget); + if (parentWidgets.containsKey(parentWidget)) { + parentWidgets.remove(parentWidget); + } + } + } + } + + super.internalRemove(elementsOrPaths); + } + + /** + * Expands all ancestors of the given element or tree path so that the given element becomes + * visible in this viewer's tree control, and then expands the subtree rooted at the given + * element to the given level. The element will be then selected. + * + * @param elementOrTreePath + * the element + * @param level + * non-negative level, or ALL_LEVELS to expand all levels of the tree + */ + public void expandToObjectAndSelect(Object elementOrTreePath, int level) { + if (checkBusy()) { + return; + } + Object parent = getParentElement(elementOrTreePath); + // check if the element is already visible, or if it is root + if (parent != null && getExpandedState(parent) || isRootElement(elementOrTreePath)) { + // then only set selection + Widget w = internalGetWidgetToSelect(elementOrTreePath); + if (null != w) { + // if widget is already available selected it + List selectionList = new ArrayList(); + selectionList.add(w); + setSelection(selectionList); + // and overwrite any earlier set selection object + objectToSelect.set(null); + } else { + // otherwise set object to selected + objectToSelect.set(elementOrTreePath); + } + } else { + // get all the objects that need to be expanded so that object is visible + objectToSelect.set(elementOrTreePath); + List objectsToExpand = createObjectList(parent, new ArrayList()); + if (!objectsToExpand.isEmpty()) { + objectsToBeExpanded.addAll(objectsToExpand); + Widget w = internalExpand(elementOrTreePath, true); + if (w != null) { + internalExpandToLevel(w, level); + } + } else { + // if the list if empty, this means that no object in the tree has to load the + // children, and they are all expanded, thus we can just select the wanted object + Widget w = internalGetWidgetToSelect(elementOrTreePath); + if (null != w) { + // if widget is here available + List selectionList = new ArrayList(); + selectionList.add(w); + setSelection(selectionList); + // and overwrite any earlier set selection object + objectToSelect.set(null); + } + } + } + } + + /** + * Expands all ancestors of the given element or tree path so that the given element becomes + * visible in this viewer's tree control and additionally expands the element if it has + * children. + * + * @param elementOrTreePath + * the element + * @param level + * non-negative level, or ALL_LEVELS to expand all levels of the tree + */ + public void expandObject(Object elementOrTreePath, int level) { + if (checkBusy()) { + return; + } + Object parent = getParentElement(elementOrTreePath); + // check if the element is already visible, or if it is root + if (!((parent != null && getExpandedState(parent)) || isRootElement(elementOrTreePath))) { + // get all the objects that need to be expanded so that object is visible + List objectsToExpand = createObjectList(parent, new ArrayList()); + if (!objectsToExpand.isEmpty()) { + objectsToBeExpanded.addAll(objectsToExpand); + } + } + objectsToBeExpanded.add(elementOrTreePath); + Widget w = internalExpand(elementOrTreePath, true); + if (w != null) { + internalExpandToLevel(w, level); + } + } + + /** + * Constructs the list of elements that need to be expanded, so that object supplied can be + * visible. + * + * @param object + * Object that expansion should reach. + * @param objectList + * List where the results are stored. + * @return List of objects for expansion. + */ + private List createObjectList(Object object, List objectList) { + if (areFiltersPassed(object) && !getExpandedState(object)) { + if (childrenLoaded(object)) { + // if children are loaded for this object we simply expand it directly + expandToLevel(object, 1); + } else { + if (objectList == null) { + objectList = new ArrayList(); + } + objectList.add(object); + } + } + Object parent = getParentElement(object); + if (null != parent) { + createObjectList(parent, objectList); + } + return objectList; + } + + /** + * Returns if all the filters are passed for the specific object. + * + * @param object + * Object to test. + * @return True if all the filters are passed, and thus object is visible in the tree. False + * otherwise. + */ + private boolean areFiltersPassed(Object object) { + ViewerFilter[] filters = getFilters(); + if (null != filters) { + for (ViewerFilter filer : filters) { + if (!filer.select(this, getParentElement(object), object)) { + return false; + } + } + } + return true; + } + + /** + * Tests if the children of the tree item have been loaded. + * + * @param object + * Object to test. + * @return True if the children have been fetched. + */ + private boolean childrenLoaded(Object object) { + Item[] children = getChildren(doFindItem(object)); + if (null == children) { + return false; + } + for (Item item : children) { + if (!(item instanceof TreeItem)) { + return false; + } + } + return true; + } + + /** + * Checks if the given element is one of the root object in the input list of the tree viewer. + * + * @param element + * Element to check. + * @return True if the element is one of the root objects. + */ + private boolean isRootElement(Object element) { + Object input = getRoot(); + Object[] rootElemens = ((ITreeContentProvider) getContentProvider()).getElements(input); + return ArrayUtils.contains(rootElemens, element); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/SteppingTreeSubView.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/SteppingTreeSubView.java new file mode 100644 index 000000000..090d348c6 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/SteppingTreeSubView.java @@ -0,0 +1,585 @@ +package info.novatec.inspectit.rcp.editor.tree; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.preferences.IPreferenceGroup; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.tree.input.SteppingTreeInputController; +import info.novatec.inspectit.rcp.util.ElementOccurrenceCount; + +import java.util.List; +import java.util.Map; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * View that enables locating the element in the tree via {@link SteppingControl}. + * + * @author Ivan Senic + * + */ +public class SteppingTreeSubView extends TreeSubView { + + /** + * Main composite for this view. It holds the {@link org.eclipse.jface.viewers.TreeViewer} and + * additionally {@link SteppingControl} if necessary. + */ + private Composite subComposite; + + /** + * Stepping control. + */ + private SteppingControl steppingControl; + + /** + * Input controller for this view. + */ + private SteppingTreeInputController steppingTreeInputController; + + /** + * Default constructor. + * + * @param treeInputController + * Stepping tree input controller. + * @see TreeSubView#TreeSubView(info.novatec.inspectit.rcp.editor.tree.input.TreeInputController) + */ + public SteppingTreeSubView(SteppingTreeInputController treeInputController) { + super(treeInputController); + + this.steppingTreeInputController = treeInputController; + } + + /** + * {@inheritDoc} + */ + @Override + public void createPartControl(Composite parent, FormToolkit toolkit) { + subComposite = toolkit.createComposite(parent); + GridLayout layout = new GridLayout(1, true); + layout.marginWidth = 0; + layout.marginHeight = 0; + subComposite.setLayout(layout); + + GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true); + subComposite.setLayoutData(gd); + + super.createPartControl(subComposite, toolkit); + gd = new GridData(SWT.FILL, SWT.FILL, true, true); + getTreeViewer().getTree().setLayoutData(gd); + + if (steppingControl == null) { + steppingControl = new SteppingControl(subComposite, toolkit, steppingTreeInputController.getSteppingObjectList()); + } + + if (steppingTreeInputController.initSteppingControlVisible() && null != steppingTreeInputController.getSteppingObjectList() && !steppingTreeInputController.getSteppingObjectList().isEmpty()) { + steppingControl.showControl(); + } + + // the focus has to be passed to the subComposite, because it can not register it + getTreeViewer().getTree().addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + subComposite.notifyListeners(SWT.FocusIn, null); + } + }); + + } + + /** + * {@inheritDoc} + */ + @Override + public Control getControl() { + return subComposite; + } + + /** + * {@inheritDoc} + */ + @Override + public void setDataInput(List data) { + super.setDataInput(data); + steppingControl.inputChanged(); + } + + /** + * {@inheritDoc} + */ + @Override + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + super.preferenceEventFired(preferenceEvent); + switch (preferenceEvent.getPreferenceId()) { + case STEPPABLE_CONTROL: + Map preferenceMap = preferenceEvent.getPreferenceMap(); + Object isChecked = preferenceMap.get(PreferenceId.SteppableControl.BUTTON_STEPPABLE_CONTROL_ID); + if (isChecked instanceof Boolean) { + Boolean makeControlVisible = (Boolean) isChecked; + if (makeControlVisible) { + steppingControl.showControl(); + } else { + steppingControl.hideControl(); + } + } + break; + case CLEAR_BUFFER: + case FILTERDATATYPE: + case INVOCFILTEREXCLUSIVETIME: + case INVOCFILTERTOTALTIME: + steppingControl.inputChanged(); + break; + default: + break; + } + } + + /** + * Adds new element to the stepping control. This method will also register the new object with + * the {@link SteppingTreeInputController}. + * + * @param element + * Object to be added. + */ + public void addObjectToSteppingControl(Object element) { + steppingTreeInputController.addObjectToSteppingObjectList(element); + if (steppingControl.isControlShown()) { + steppingControl.inputChanged(); + steppingControl.selectObject(element); + } else { + steppingControl.showControl(); + } + } + + /** + * Alters the state of the stepping control button on preference panel. + * + * @param checked + * Should button be checked or not. + */ + private void setSwitchSteppingControlButtonChecked(boolean checked) { + this.getRootEditor().getPreferencePanel().setSteppingControlChecked(checked); + } + + /** + * Tries to expand the tree viewer to the wanted occurrence of wanted element. If the wanted + * occurrence is not reachable, nothing is done. Otherwise the tree is expanded and element + * selected. + * + * @param template + * Element to reach. + * @param occurrence + * Wanted occurrence in the tree. + */ + private void expandToObject(Object template, int occurrence) { + Object realElement = steppingTreeInputController.getElement(template, occurrence, getTreeViewer().getFilters()); + if (null != realElement) { + ((DeferredTreeViewer) getTreeViewer()).expandToObjectAndSelect(realElement, 0); + } + } + + /** + * Counts total occurrences found for given element. This method is just delegating the call to + * the {@link SteppingTreeInputController}. Result depends on the filters that are currently + * active for the tree. + * + * @param element + * Element to count occurrences. + * @return Total number of elements found. + */ + private ElementOccurrenceCount countOccurrences(Object element) { + return steppingTreeInputController.countOccurrences(element, getTreeViewer().getFilters()); + } + + /** + * Is input set for this sub view. + * + * @return True is input is not null or if it is not empty. Otherwise false. + */ + @SuppressWarnings("unchecked") + private boolean isInputSet() { + List input = (List) getTreeViewer().getInput(); + if (input == null || input.isEmpty()) { + return false; + } + return true; + } + + /** + * Stepping control class. + * + * @author Ivan Senic + * + */ + private class SteppingControl { + + /** + * Composite where stepping control will be created. + */ + private Composite parent; + + /** + * Toolkit. + */ + private FormToolkit toolkit; + + /** + * List of objects that are able to be located in the tree. + */ + private List steppableObjects; + + /** + * List of objects that are able currently in the combo. + */ + private List objectsInCombo; + + /** + * Main composite of stepping control. + */ + private Composite mainComposite; + + /** + * Combo for object selection. + */ + private Combo objectSelection; + + /** + * Next button. + */ + private Button next; + + /** + * Previous button. + */ + private Button previous; + + /** + * Clear all button. + */ + private Button clearAllButton; + + /** + * Information label. + */ + private Label info; + + /** + * Flag for defining is the control show or not. + */ + private boolean controlShown = false; + + /** + * The currently selected object that is to be found in the tree. + */ + private Object selectedObject; + + /** + * Current displayed occurrence of selected object. + */ + private int occurrence; + + /** + * Visible occurrence of the selected object that could be reached. + */ + private int visibleOccurrences; + + /** + * Filtered occurrence of the selected object that could not be reached. + */ + private int filteredOccurrences; + + /** + * Default constructor. + * + * @param parent + * Composite where stepping control will be created. + * @param toolkit + * Toolkit. + * @param objectList + * List of objects that are able to be located in the tree. + */ + public SteppingControl(Composite parent, FormToolkit toolkit, List objectList) { + super(); + this.parent = parent; + this.toolkit = toolkit; + this.steppableObjects = objectList; + } + + /** + * Creates stepping control. + * + * @param parent + * Composite where stepping control will be created. + * @param toolkit + * Toolkit. + */ + private void createPartControl(Composite parent, FormToolkit toolkit) { + mainComposite = toolkit.createComposite(parent); + GridLayout layout = new GridLayout(7, false); + mainComposite.setLayout(layout); + mainComposite.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false)); + + toolkit.createLabel(mainComposite, "Object to locate:"); + + objectSelection = new Combo(mainComposite, SWT.SIMPLE | SWT.DROP_DOWN | SWT.READ_ONLY); + GridData gd = new GridData(); + gd.grabExcessHorizontalSpace = true; + gd.horizontalAlignment = GridData.FILL; + gd.minimumWidth = 200; + objectSelection.setLayoutData(gd); + + previous = toolkit.createButton(mainComposite, "Previous", SWT.PUSH | SWT.NO_BACKGROUND); + previous.setEnabled(false); + previous.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_PREVIOUS)); + + next = toolkit.createButton(mainComposite, "Next", SWT.PUSH | SWT.NO_BACKGROUND); + next.setEnabled(false); + next.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_NEXT)); + + info = toolkit.createLabel(mainComposite, "No invocation loaded"); + + // added additional composite to the right, so that minimizing and maximizing the window + // can look better + Composite helpComposite = toolkit.createComposite(mainComposite); + gd = new GridData(); + gd.grabExcessHorizontalSpace = true; + gd.horizontalAlignment = GridData.FILL; + gd.minimumWidth = 0; + gd.heightHint = 0; + gd.widthHint = 0; + helpComposite.setLayoutData(gd); + + clearAllButton = toolkit.createButton(mainComposite, "", SWT.PUSH | SWT.NO_BACKGROUND); + clearAllButton.setEnabled(false); + clearAllButton.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_TRASH)); + clearAllButton.setToolTipText("Empty steppable objects list"); + + objectSelection.addListener(SWT.Modify, new Listener() { + @Override + public void handleEvent(Event event) { + int selectionIndex = objectSelection.getSelectionIndex(); + if (selectionIndex != -1) { + Object selObject = objectsInCombo.get(selectionIndex); + selectedObject = selObject; + if (isInputSet()) { + occurrence = 0; + ElementOccurrenceCount elementOccurrenceCount = countOccurrences(selectedObject); + visibleOccurrences = elementOccurrenceCount.getVisibleOccurrences(); + filteredOccurrences = elementOccurrenceCount.getFilteredOccurrences(); + if (visibleOccurrences > 0) { + expandToObject(selectedObject, ++occurrence); + } + if (visibleOccurrences <= occurrence) { + next.setEnabled(false); + } else { + next.setEnabled(true); + } + if (occurrence <= 1) { + previous.setEnabled(false); + } else { + previous.setEnabled(true); + } + } + } else { + next.setEnabled(false); + previous.setEnabled(false); + } + updateInfoBox(); + } + }); + + next.addListener(SWT.Selection, new Listener() { + @Override + public void handleEvent(Event event) { + expandToObject(selectedObject, ++occurrence); + if (visibleOccurrences <= occurrence) { + next.setEnabled(false); + } + if (occurrence <= 1) { + previous.setEnabled(false); + } else { + previous.setEnabled(true); + } + updateInfoBox(); + } + }); + + previous.addListener(SWT.Selection, new Listener() { + @Override + public void handleEvent(Event event) { + expandToObject(selectedObject, --occurrence); + next.setEnabled(true); + if (occurrence <= 1) { + previous.setEnabled(false); + } + updateInfoBox(); + } + }); + + clearAllButton.addListener(SWT.Selection, new Listener() { + @Override + public void handleEvent(Event event) { + clearAll(); + } + }); + + controlShown = true; + } + + /** + * Clears all objects from the list. + */ + private void clearAll() { + steppableObjects.clear(); + objectSelection.removeAll(); + objectsInCombo.clear(); + selectedObject = null; // NOPMD + occurrence = 0; + inputChanged(); + } + + /** + * + * + * @param object + * One of the objects that are to be located in the tree. + * @return Returns the string to be inserted into the combo box for supplied object. + */ + private String getTextualString(Object object) { + String representation = steppingTreeInputController.getElementTextualRepresentation(object); + // Assure that string is not too long + if (representation.length() > 120) { + representation = representation.substring(0, 118) + ".."; + } + ElementOccurrenceCount elementOccurrenceCount = countOccurrences(object); + return representation + " (" + elementOccurrenceCount.getVisibleOccurrences() + " visible, " + elementOccurrenceCount.getFilteredOccurrences() + " filtered)"; + } + + /** + * Selects the given object in the stepping control, if the object is currently in the + * combo-box. + * + * @param element + * Element to select. + */ + public void selectObject(Object element) { + if (controlShown) { + int index = objectsInCombo.indexOf(element); + if (index != -1) { + objectSelection.select(index); + } + } + } + + /** + * Hides stepping control. + */ + public void hideControl() { + if (controlShown) { + mainComposite.dispose(); + subComposite.layout(); + controlShown = false; + setSwitchSteppingControlButtonChecked(false); + } + } + + /** + * Shows stepping control. + */ + public void showControl() { + if (!controlShown) { + createPartControl(parent, toolkit); + subComposite.layout(); + controlShown = true; + inputChanged(); + setSwitchSteppingControlButtonChecked(true); + } + } + + /** + * Resets stepping control. + */ + public void inputChanged() { + if (controlShown) { + if (isInputSet()) { + objectsInCombo = steppableObjects; + objectSelection.removeAll(); + if (!objectsInCombo.isEmpty()) { + clearAllButton.setEnabled(true); + for (Object object : objectsInCombo) { + objectSelection.add(getTextualString(object)); + } + objectSelection.pack(true); + if (null != selectedObject && objectsInCombo.contains(selectedObject)) { + objectSelection.select(objectsInCombo.indexOf(selectedObject)); + } else { + objectSelection.select(0); + } + } else { + next.setEnabled(false); + previous.setEnabled(false); + clearAllButton.setEnabled(false); + updateInfoBox(); + } + } else { + objectSelection.removeAll(); + next.setEnabled(false); + previous.setEnabled(false); + updateInfoBox(); + } + mainComposite.layout(); + } + } + + /** + * Updates the text in the info box based on the current status of the stepping control. + */ + private void updateInfoBox() { + if (controlShown) { + String msg = ""; + if (isInputSet() && objectSelection.getSelectionIndex() != -1) { + if (occurrence == 0 && visibleOccurrences != 0) { + msg = "Found " + visibleOccurrences + " occurrence"; + if (visibleOccurrences > 1) { + msg += "s"; + } + + } else if (occurrence != 0) { + msg = occurrence + "/" + visibleOccurrences; + } else { + msg = "No occurrences found"; + } + if (filteredOccurrences > 0) { + msg += " (" + filteredOccurrences + " filtered out)"; + } + } else if (objectSelection.getItemCount() == 0) { + msg = "No object to locate"; + } else { + msg = "No invocation loaded"; + } + info.setText(msg); + mainComposite.layout(); + } + } + + /** + * @return the controlShown + */ + public boolean isControlShown() { + return controlShown; + } + + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/TreeSubView.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/TreeSubView.java new file mode 100644 index 000000000..deb3b14be --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/TreeSubView.java @@ -0,0 +1,475 @@ +package info.novatec.inspectit.rcp.editor.tree; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.editor.AbstractSubView; +import info.novatec.inspectit.rcp.editor.ISubView; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.root.FormRootEditor; +import info.novatec.inspectit.rcp.editor.root.SubViewClassificationController.SubViewClassification; +import info.novatec.inspectit.rcp.editor.search.ISearchExecutor; +import info.novatec.inspectit.rcp.editor.search.criteria.SearchCriteria; +import info.novatec.inspectit.rcp.editor.search.criteria.SearchResult; +import info.novatec.inspectit.rcp.editor.search.helper.DeferredTreeViewerSearchHelper; +import info.novatec.inspectit.rcp.editor.tooltip.ColumnAwareToolTipSupport; +import info.novatec.inspectit.rcp.editor.tooltip.IColumnToolTipProvider; +import info.novatec.inspectit.rcp.editor.tree.input.TreeInputController; +import info.novatec.inspectit.rcp.handlers.ShowHideColumnsHandler; +import info.novatec.inspectit.rcp.menu.ShowHideMenuManager; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.apache.commons.lang.ArrayUtils; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.jface.viewers.TreeSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeColumn; +import org.eclipse.ui.forms.widgets.FormToolkit; + +/** + * Sub-view which is used to create a tree. + * + * @author Patrice Bouillet + * + */ +public class TreeSubView extends AbstractSubView implements ISearchExecutor { + + /** + * The referenced input controller. + */ + private final TreeInputController treeInputController; + + /** + * The created tree viewer. + */ + private TreeViewer treeViewer; + + /** + * Defines if a job is currently already executing. + */ + private volatile boolean jobInSchedule = false; + + /** + * {@link DeferredTreeViewerSearchHelper}. + */ + private DeferredTreeViewerSearchHelper searchHelper; + + /** + * Default constructor which needs a tree input controller to create all the content etc. + * + * @param treeInputController + * The tree input controller. + */ + public TreeSubView(TreeInputController treeInputController) { + Assert.isNotNull(treeInputController); + + this.treeInputController = treeInputController; + } + + /** + * {@inheritDoc} + */ + @Override + public void init() { + treeInputController.setInputDefinition(getRootEditor().getInputDefinition()); + } + + /** + * {@inheritDoc} + */ + public void createPartControl(Composite parent, FormToolkit toolkit) { + final Tree tree = toolkit.createTree(parent, SWT.MULTI | SWT.FULL_SELECTION | SWT.VIRTUAL | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); + tree.setHeaderVisible(true); + + treeViewer = new DeferredTreeViewer(tree); + treeInputController.createColumns(treeViewer); + treeViewer.setUseHashlookup(true); + treeViewer.setContentProvider(treeInputController.getContentProvider()); + IBaseLabelProvider labelProvider = treeInputController.getLabelProvider(); + treeViewer.setLabelProvider(labelProvider); + if (labelProvider instanceof IColumnToolTipProvider) { + ColumnAwareToolTipSupport.enableFor(treeViewer); + } + treeViewer.addDoubleClickListener(new IDoubleClickListener() { + public void doubleClick(DoubleClickEvent event) { + treeInputController.doubleClick(event); + TreeSelection selection = (TreeSelection) event.getSelection(); + TreePath path = selection.getPaths()[0]; + if (null != path) { + boolean expanded = treeViewer.getExpandedState(path); + if (expanded) { + treeViewer.collapseToLevel(path, 1); + } else { + treeViewer.expandToLevel(path, 1); + } + } + } + }); + treeViewer.setComparator(treeInputController.getComparator()); + if (null != treeViewer.getComparator()) { + TreeColumn[] treeColumns = treeViewer.getTree().getColumns(); + for (TreeColumn treeColumn : treeColumns) { + treeColumn.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + treeViewer.refresh(); + } + }); + } + } + if (ArrayUtils.isNotEmpty(treeInputController.getFilters())) { + treeViewer.setFilters(treeInputController.getFilters()); + } + + // add show hide columns support + MenuManager headerMenuManager = new ShowHideMenuManager(treeViewer, treeInputController.getClass()); + headerMenuManager.setRemoveAllWhenShown(false); + + // normal selection menu manager + MenuManager selectionMenuManager = new MenuManager(); + selectionMenuManager.setRemoveAllWhenShown(true); + getRootEditor().getSite().registerContextMenu(FormRootEditor.ID + ".treesubview", selectionMenuManager, treeViewer); + + final Menu selectionMenu = selectionMenuManager.createContextMenu(tree); + final Menu headerMenu = headerMenuManager.createContextMenu(tree); + + tree.addListener(SWT.MenuDetect, new Listener() { + @Override + public void handleEvent(Event event) { + Point pt = Display.getDefault().map(null, tree, new Point(event.x, event.y)); + Rectangle clientArea = tree.getClientArea(); + boolean header = clientArea.y <= pt.y && pt.y < (clientArea.y + tree.getHeaderHeight()); + if (header) { + tree.setMenu(headerMenu); + } else { + tree.setMenu(selectionMenu); + } + } + }); + + /** + * IMPORTANT: Only the menu set in the setMenu() will be disposed automatically. + */ + tree.addListener(SWT.Dispose, new Listener() { + @Override + public void handleEvent(Event event) { + if (!headerMenu.isDisposed()) { + headerMenu.dispose(); + } + if (!selectionMenu.isDisposed()) { + selectionMenu.dispose(); + } + } + }); + + Object input = treeInputController.getTreeInput(); + treeViewer.setInput(input); + + ControlAdapter columnResizeListener = new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + if (e.widget instanceof TreeColumn) { + TreeColumn column = (TreeColumn) e.widget; + if (column.getWidth() > 0) { + ShowHideColumnsHandler.registerNewColumnWidth(treeInputController.getClass(), column.getText(), column.getWidth()); + } + } + } + + @Override + public void controlMoved(ControlEvent e) { + ShowHideColumnsHandler.setColumnOrder(treeInputController.getClass(), treeViewer.getTree().getColumnOrder()); + } + }; + + for (TreeColumn column : tree.getColumns()) { + if (treeInputController.canAlterColumnWidth(column)) { + Integer rememberedWidth = ShowHideColumnsHandler.getRememberedColumnWidth(treeInputController.getClass(), column.getText()); + boolean isColumnHidden = ShowHideColumnsHandler.isColumnHidden(treeInputController.getClass(), column.getText()); + + if (rememberedWidth != null && !isColumnHidden) { + column.setWidth(rememberedWidth.intValue()); + column.setResizable(true); + } else if (isColumnHidden) { + column.setWidth(0); + column.setResizable(false); + } + } + + column.addControlListener(columnResizeListener); + } + + // update the order of columns if the order was defined for the class, and no new columns + // were added + int[] columnOrder = ShowHideColumnsHandler.getColumnOrder(treeInputController.getClass()); + if (null != columnOrder && columnOrder.length == tree.getColumns().length) { + tree.setColumnOrder(columnOrder); + } else if (null != columnOrder) { + // if the order exists, but length is not same, then update with the default order + ShowHideColumnsHandler.setColumnOrder(treeInputController.getClass(), tree.getColumnOrder()); + } + + // create search helper + searchHelper = new DeferredTreeViewerSearchHelper((DeferredTreeViewer) treeViewer, treeInputController, getRootEditor().getInputDefinition().getRepositoryDefinition()); + } + + /** + * {@inheritDoc} + */ + public void doRefresh() { + if (!jobInSchedule) { + jobInSchedule = true; + + Job job = new Job(getDataLoadingJobName()) { + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + treeInputController.doRefresh(monitor, getRootEditor()); + Display.getDefault().asyncExec(new Runnable() { + public void run() { + if (checkDisposed()) { + return; + } + + // refresh should only influence the master sub views + if (treeInputController.getSubViewClassification() == SubViewClassification.MASTER) { + Object input = treeInputController.getTreeInput(); + treeViewer.setInput(input); + if (treeViewer.getTree().isVisible()) { + treeViewer.refresh(); + treeViewer.expandToLevel(treeInputController.getExpandLevel()); + } + } + } + }); + } catch (Throwable throwable) { // NOPMD + throw new RuntimeException("Unknown exception occurred trying to refresh the view.", throwable); + } finally { + jobInSchedule = false; + } + return Status.OK_STATUS; + } + }; + job.schedule(); + } + } + + /** + * {@inheritDoc} + */ + public void setDataInput(List data) { + if (checkDisposed()) { + return; + } + + if (treeInputController.canOpenInput(data)) { + treeViewer.setInput(data); + treeViewer.expandToLevel(treeInputController.getExpandLevel()); + // i will comment this out because tree viewer is not refreshing if it is not visible, + // meaning when i clear buffer only tree views that are visible are cleared. + // if (treeViewer.getControl().isVisible()) { + treeViewer.refresh(); + // } + } + } + + /** + * {@inheritDoc} + */ + public Control getControl() { + return treeViewer.getControl(); + } + + /** + * {@inheritDoc} + */ + public ISelectionProvider getSelectionProvider() { + return treeViewer; + } + + /** + * {@inheritDoc} + */ + public Set getPreferenceIds() { + return treeInputController.getPreferenceIds(); + } + + /** + * {@inheritDoc} + */ + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + if (checkDisposed()) { + return; + } + + treeInputController.preferenceEventFired(preferenceEvent); + switch (preferenceEvent.getPreferenceId()) { + case FILTERDATATYPE: + case INVOCFILTEREXCLUSIVETIME: + case INVOCFILTERTOTALTIME: + // we have to re-apply the filter if there is one + if (ArrayUtils.isNotEmpty(treeInputController.getFilters())) { + treeViewer.setFilters(treeInputController.getFilters()); + } + break; + case CLEAR_BUFFER: + if (treeInputController.getPreferenceIds().contains(PreferenceId.CLEAR_BUFFER)) { + treeViewer.refresh(); + treeViewer.expandToLevel(treeInputController.getExpandLevel()); + } + break; + case TIME_RESOLUTION: + if (treeInputController.getPreferenceIds().contains(PreferenceId.TIME_RESOLUTION)) { + treeViewer.refresh(); + treeViewer.expandToLevel(treeInputController.getExpandLevel()); + } + break; + case INVOCATION_SUBVIEW_MODE: + if (treeInputController.getPreferenceIds().contains(PreferenceId.INVOCATION_SUBVIEW_MODE)) { + treeViewer.refresh(); + treeViewer.expandToLevel(treeInputController.getExpandLevel()); + } + break; + default: + break; + } + } + + /** + * Returns the tree viewer. + * + * @return The tree viewer. + */ + public TreeViewer getTreeViewer() { + return treeViewer; + } + + /** + * Returns the tree input controller. + * + * @return The tree input controller. + */ + public TreeInputController getTreeInputController() { + return treeInputController; + } + + /** + * Return the names of all columns in the tree. Not visible columns names will also be included. + * The order of the names will be same to the initial tree column order, thus not reflecting the + * current state of the table if the columns were moved. + * + * @return List of column names. + */ + public List getColumnNames() { + List names = new ArrayList(); + for (TreeColumn column : treeViewer.getTree().getColumns()) { + names.add(column.getText()); + } + return names; + } + + /** + * + * @return The list of integers representing the column order in the tree. Note that only + * columns that are currently visible will be included in the list. + * @see org.eclipse.swt.widgets.Table#getColumnOrder() + */ + public List getColumnOrder() { + int[] order = treeViewer.getTree().getColumnOrder(); + List orderWithoutHidden = new ArrayList(); + for (int index : order) { + if (treeViewer.getTree().getColumns()[index].getWidth() > 0) { + orderWithoutHidden.add(index); + } + } + return orderWithoutHidden; + } + + /** + * {@inheritDoc} + */ + @Override + public ISubView getSubViewWithInputController(Class inputControllerClass) { + if (Objects.equals(inputControllerClass, treeInputController.getClass())) { + return this; + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public SearchResult executeSearch(SearchCriteria searchCriteria) { + return searchHelper.executeSearch(searchCriteria); + } + + /** + * {@inheritDoc} + */ + @Override + public SearchResult next() { + return searchHelper.next(); + } + + /** + * {@inheritDoc} + */ + @Override + public SearchResult previous() { + return searchHelper.previous(); + } + + /** + * {@inheritDoc} + */ + @Override + public void clearSearch() { + searchHelper.clearSearch(); + } + + /** + * Returns true if the tree in the sub-view is disposed. False otherwise. + * + * @return Returns true if the tree in the sub-view is disposed. False otherwise. + */ + private boolean checkDisposed() { + return treeViewer.getTree().isDisposed(); + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + treeInputController.dispose(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/TreeViewerComparator.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/TreeViewerComparator.java new file mode 100644 index 000000000..e28793130 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/TreeViewerComparator.java @@ -0,0 +1,44 @@ +package info.novatec.inspectit.rcp.editor.tree; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.rcp.editor.viewers.AbstractViewerComparator; + +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeColumn; + +/** + * Local table viewer comparator uses provided comparators to sort specific columns. + * + * @author Ivan Senic + * + * @param + * Type for which comparator is created. + */ +public class TreeViewerComparator extends AbstractViewerComparator { + + /** + * Adds a column to this comparator so it can be used to sort by. + * + * @param column + * The {@link TreeColumn} implementation. comparatorProvider The id of the + * {@link TableColumn} (user-defined). + * @param comparator + * Comparator that will be used for the given column. + */ + public final void addColumn(final TreeColumn column, final ResultComparator comparator) { + column.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + toggleSortColumn(comparator); + + Tree tree = column.getParent(); + tree.setSortColumn(column); + tree.setSortDirection(getSortState().getSwtDirection()); + } + }); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/AbstractTreeInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/AbstractTreeInputController.java new file mode 100644 index 000000000..2b864e795 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/AbstractTreeInputController.java @@ -0,0 +1,248 @@ +package info.novatec.inspectit.rcp.editor.tree.input; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.TreeViewerColumn; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.swt.widgets.TreeColumn; + +/** + * The abstract class of the {@link TreeInputController} interface to provide some standard methods. + * + * @author Eduard Tudenhoefner + * + */ +public abstract class AbstractTreeInputController implements TreeInputController { + + /** + * Map of the enumeration keys and {@link TreeViewerColumn}s. Subclasses can use utility methods + * to bound columns for later use. + */ + private Map, TreeViewerColumn> treeViewerColumnMap = new HashMap, TreeViewerColumn>(); + + /** + * The input definition. + */ + private InputDefinition inputDefinition; + + /** + * {@inheritDoc} + */ + public void setInputDefinition(InputDefinition inputDefinition) { + Assert.isNotNull(inputDefinition); + + this.inputDefinition = inputDefinition; + } + + /** + * Returns the input definition. + * + * @return The input definition. + */ + protected InputDefinition getInputDefinition() { + Assert.isNotNull(inputDefinition); + + return inputDefinition; + } + + /** + * {@inheritDoc} + *

+ * Return null by default, sub-classes may override. + */ + public boolean canOpenInput(List data) { + return false; + } + + /** + * {@inheritDoc} + *

+ * Do nothing by default, sub-classes may override. + */ + public void createColumns(TreeViewer treeViewer) { + } + + /** + * {@inheritDoc} + *

+ * Do nothing by default, sub-classes may override. + */ + public void doRefresh(IProgressMonitor monitor, IRootEditor rootEditor) { + } + + /** + * {@inheritDoc} + *

+ * Do nothing by default, sub-classes may override. + */ + public void doubleClick(DoubleClickEvent event) { + } + + /** + * {@inheritDoc} + *

+ * Return null by default, sub-classes may override. + */ + public ViewerComparator getComparator() { + return null; + } + + /** + * {@inheritDoc} + *

+ * Return null by default, sub-classes may override. + */ + public IContentProvider getContentProvider() { + return null; + } + + /** + * {@inheritDoc} + *

+ * Return null by default, sub-classes may override. + */ + public ViewerFilter[] getFilters() { + return new ViewerFilter[0]; + } + + /** + * {@inheritDoc} + *

+ * Return null by default, sub-classes may override. + */ + public IBaseLabelProvider getLabelProvider() { + return null; + } + + /** + * {@inheritDoc} + *

+ * Return an empty set by default, sub-classes may override. + */ + public Set getPreferenceIds() { + return Collections.emptySet(); + } + + /** + * {@inheritDoc} + *

+ * Return null by default, sub-classes may override. + */ + public String getReadableString(Object object) { + return null; + } + + /** + * {@inheritDoc} + *

+ * Return null by default, sub-classes may override. + */ + public Object getTreeInput() { + return null; + } + + /** + * {@inheritDoc} + *

+ * Do nothing by default, sub-classes may override. + */ + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + } + + /** + * {@inheritDoc} + *

+ * Return 2 by default, sub-classes may override. + */ + public int getExpandLevel() { + return 2; + } + + /** + * {@inheritDoc} + */ + @Override + public Object[] getObjectsToSearch(Object treeInput) { + if (treeInput instanceof Object[]) { + return (Object[]) treeInput; + } + if (treeInput instanceof Collection) { + return ((Collection) treeInput).toArray(); + } + return new Object[0]; + } + + /** + * {@inheritDoc} + *

+ * Do nothing by default, sub-classes may override. + */ + public void dispose() { + } + + /** + * {@inheritDoc} + *

+ * By default controller sets the sub view to be master. + */ + @Override + public SubViewClassification getSubViewClassification() { + return SubViewClassification.MASTER; + } + + /** + * {@inheritDoc} + *

+ * Returns true, classes may override. + */ + @Override + public boolean canAlterColumnWidth(TreeColumn treeColumn) { + return true; + } + + /** + * Maps a column with the enumeration key. The implementing classes should map each column they + * create to the enum that represents that column. Later on the column can be retrieved with the + * enum key if needed. + * + * @param key + * Enumeration that represents the column. + * @param column + * Created column to be mapped. + */ + public void mapTreeViewerColumn(Enum key, TreeViewerColumn column) { + treeViewerColumnMap.put(key, column); + } + + /** + * Returns the column that has been mapped with the given enum key. Enum should represent the + * wanted column. + * + * @param key + * Enumeration that represents the column. + * @return Returns the column that has been mapped with the given enum key or null + * if no mapping has been done. + */ + public TreeViewerColumn getMappedTreeViewerColumn(Enum key) { + return treeViewerColumnMap.get(key); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/DeferredInvoc.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/DeferredInvoc.java new file mode 100644 index 000000000..698c1249e --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/DeferredInvoc.java @@ -0,0 +1,100 @@ +package info.novatec.inspectit.rcp.editor.tree.input; + +import info.novatec.inspectit.communication.data.InvocationSequenceData; + +import java.util.List; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.ui.progress.IDeferredWorkbenchAdapter; +import org.eclipse.ui.progress.IElementCollector; + +/** + * This class is used to create the UI elements of tree-based views only if the parent element is + * said to be opened. + * + * @author Patrice Bouillet + * + */ +public class DeferredInvoc implements IDeferredWorkbenchAdapter { + + /** + * Defines the items per loop. + */ + public static final int ITEMS_PER_LOOP = 50; + + /** + * {@inheritDoc} + */ + public void fetchDeferredChildren(Object object, IElementCollector collector, IProgressMonitor monitor) { + InvocationSequenceData parentData = (InvocationSequenceData) object; + List nestedSequences = parentData.getNestedSequences(); + monitor.beginTask("Loading of Invocation Sequence Data Objects...", nestedSequences.size()); + + for (int i = 0; i < nestedSequences.size(); i = i + ITEMS_PER_LOOP) { + List subList; + if (i + ITEMS_PER_LOOP > nestedSequences.size()) { + subList = nestedSequences.subList(i, nestedSequences.size()); + } else { + subList = nestedSequences.subList(i, i + ITEMS_PER_LOOP); + } + monitor.worked(subList.size()); + + collector.add(subList.toArray(), monitor); + + if (monitor.isCanceled()) { + break; + } + } + + monitor.done(); + } + + /** + * {@inheritDoc} + */ + public ISchedulingRule getRule(Object object) { + return null; + } + + /** + * {@inheritDoc} + */ + public boolean isContainer() { + return true; + } + + /** + * {@inheritDoc} + */ + public Object[] getChildren(Object o) { + InvocationSequenceData invocationSequenceData = (InvocationSequenceData) o; + List nestedSequences = invocationSequenceData.getNestedSequences(); + + return nestedSequences.toArray(); + } + + /** + * {@inheritDoc} + */ + public ImageDescriptor getImageDescriptor(Object object) { + return null; + } + + /** + * {@inheritDoc} + */ + public String getLabel(Object o) { + return null; + } + + /** + * {@inheritDoc} + */ + public Object getParent(Object o) { + InvocationSequenceData invocationSequenceData = (InvocationSequenceData) o; + return invocationSequenceData.getParentSequence(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/DeferredInvocAdapterFactory.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/DeferredInvocAdapterFactory.java new file mode 100644 index 000000000..8121320e4 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/DeferredInvocAdapterFactory.java @@ -0,0 +1,38 @@ +package info.novatec.inspectit.rcp.editor.tree.input; + +import info.novatec.inspectit.communication.data.InvocationSequenceData; + +import org.eclipse.core.runtime.IAdapterFactory; +import org.eclipse.ui.progress.IDeferredWorkbenchAdapter; + +/** + * Adapter Factory which is used to create the {@link DeferredInvoc} objects if the adaptable object + * is of type {@link InvocationSequenceData}. + * + * @author Patrice Bouillet + * + */ +public class DeferredInvocAdapterFactory implements IAdapterFactory { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("rawtypes") + public Object getAdapter(Object adaptableObject, Class adapterType) { + if (IDeferredWorkbenchAdapter.class == adapterType) { + if (adaptableObject instanceof InvocationSequenceData) { + return new DeferredInvoc(); + } + } + return null; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("rawtypes") + public Class[] getAdapterList() { + return new Class[] { IDeferredWorkbenchAdapter.class }; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/ExceptionMessagesTreeInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/ExceptionMessagesTreeInputController.java new file mode 100644 index 000000000..f398b9917 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/ExceptionMessagesTreeInputController.java @@ -0,0 +1,420 @@ +package info.novatec.inspectit.rcp.editor.tree.input; + +import info.novatec.inspectit.cmr.service.IExceptionDataAccessService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.AggregatedExceptionSensorData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.TreeViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; + +/** + * + * @author Eduard Tudenhoefner + * + */ +public class ExceptionMessagesTreeInputController extends AbstractTreeInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.tree.exceptionmessagestree"; + + /** + * Mam leght of the error message in the tree. + */ + private static final int MAX_ERROR_MSG_SIZE = 150; + + /** + * Message for stack trace not available. + */ + private static final String STACK_TRACK_NOT_AVAILABLE = "Stack track not available"; + + /** + * Message for no error message. + */ + private static final String NO_ERROR_MESSAGE_PROVIDED = "No Error Message provided"; + + /** + * Data access service for getting the stack traces. + */ + private IExceptionDataAccessService dataAccessService; + + /** + * Map used to associate parent - children objects. + */ + private Map> parentChildrenMap; + + /** + * The private inner enumeration used to define the used IDs which are mapped into the columns. + * The order in this enumeration represents the order of the columns. If it is reordered, + * nothing else has to be changed. + * + * @author Eduard Tudenhoefner + * + */ + private static enum Column { + /** The error message column. */ + ERROR_MESSAGE("Error Message with Stack Trace", 450, InspectITImages.IMG_CLASS), + /** Invocation Affiliation. */ + INVOCATION_AFFILLIATION("In Invocations", 120, InspectITImages.IMG_INVOCATION), + /** The CREATED column. */ + CREATED("Created", 70, null), + /** The RETHROWN column. */ + RETHROWN("Rethrown", 70, null), + /** The HANDLED column. */ + HANDLED("Handled", 70, null); + + /** The name. */ + private String name; + /** The width of the column. */ + private int width; + /** The image descriptor. Can be null */ + private Image image; + + /** + * Default constructor which creates a column enumeration object. + * + * @param name + * The name of the column. + * @param width + * The width of the column. + * @param imageName + * The name of the image. Names are defined in {@link InspectITImages}. + */ + private Column(String name, int width, String imageName) { + this.name = name; + this.width = width; + this.image = InspectIT.getDefault().getImage(imageName); + } + + /** + * Converts an ordinal into a column. + * + * @param i + * The ordinal. + * @return The appropriate column. + */ + public static Column fromOrd(int i) { + if (i < 0 || i >= Column.values().length) { + throw new IndexOutOfBoundsException("Invalid ordinal"); + } + return Column.values()[i]; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + dataAccessService = inputDefinition.getRepositoryDefinition().getExceptionDataAccessService(); + } + + /** + * {@inheritDoc} + */ + public void createColumns(TreeViewer treeViewer) { + for (Column column : Column.values()) { + TreeViewerColumn viewerColumn = new TreeViewerColumn(treeViewer, SWT.NONE); + viewerColumn.getColumn().setMoveable(true); + viewerColumn.getColumn().setResizable(true); + viewerColumn.getColumn().setText(column.name); + viewerColumn.getColumn().setWidth(column.width); + if (null != column.image) { + viewerColumn.getColumn().setImage(column.image); + } + } + } + + /** + * {@inheritDoc} + */ + public int getExpandLevel() { + return 0; + } + + /** + * {@inheritDoc} + */ + public IContentProvider getContentProvider() { + return new ExceptionMessagesTreeContentProvider(); + } + + /** + * {@inheritDoc} + */ + public IBaseLabelProvider getLabelProvider() { + return new ExceptionMessagesTreeLabelProvider(); + } + + /** + * {@inheritDoc} + */ + public boolean canOpenInput(List data) { + if (null == data) { + return false; + } + + if (data.isEmpty()) { + return true; + } + + if (!(data.get(0) instanceof AggregatedExceptionSensorData)) { + return false; + } + + return true; + } + + /** + * The label provider for this view. + * + * @author Eduard Tudenhoefner + * + */ + private final class ExceptionMessagesTreeLabelProvider extends StyledCellIndexLabelProvider { + + /** + * {@inheritDoc} + */ + protected StyledString getStyledText(Object element, int index) { + ExceptionSensorData data = (ExceptionSensorData) element; + Column enumId = Column.fromOrd(index); + + return getStyledTextForColumn(data, enumId); + } + } + + /** + * The content provider for this view. + * + * @author Eduard Tudenhoefner + * + */ + private final class ExceptionMessagesTreeContentProvider implements ITreeContentProvider { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public Object[] getElements(Object inputElement) { + List exceptionSensorData = (List) inputElement; + return exceptionSensorData.toArray(); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + List input = (List) newInput; + if (input != null && !input.isEmpty()) { + // we can get any one because all the ones in the list have the same throwable + // type + ExceptionSensorData template = input.get(0); + List exceptionStackTraceObjects = dataAccessService.getStackTraceMessagesForThrowableType(template); + parentChildrenMap = new HashMap>(); + int i = 1; + for (AggregatedExceptionSensorData aggExceptionSensorData : input) { + // id has to be set because if the hash value of the object in the map + // otherwise there is a chance of the same hash values + aggExceptionSensorData.setId(i++); + List children = new ArrayList(); + for (ExceptionSensorData exData : exceptionStackTraceObjects) { + if (ObjectUtils.equals(exData.getErrorMessage(), aggExceptionSensorData.getErrorMessage())) { + children.add(exData); + } + } + parentChildrenMap.put(aggExceptionSensorData, children); + } + } + } + + /** + * {@inheritDoc} + */ + public Object[] getChildren(Object parent) { + if (parent instanceof AggregatedExceptionSensorData) { + if (parentChildrenMap.containsKey(parent)) { + return parentChildrenMap.get(parent).toArray(); + } + } + + return new Object[0]; + } + + /** + * {@inheritDoc} + */ + public Object getParent(Object element) { + return null; + } + + /** + * {@inheritDoc} + */ + public boolean hasChildren(Object parent) { + if (parent instanceof AggregatedExceptionSensorData) { + if (parentChildrenMap.containsKey(parent)) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + } + + /** + * Returns styled string for {@link AggregatedExceptionSensorData}. + * + * @param data + * Data. + * @param enumId + * Column. + * @return String + */ + private StyledString getStyledTextForColumn(ExceptionSensorData data, Column enumId) { + switch (enumId) { + case ERROR_MESSAGE: + StyledString styledString; + if (parentChildrenMap.containsKey(data)) { + String errorMessage = data.getErrorMessage(); + if (null != errorMessage && !"".equals(errorMessage)) { + // if error message is provided then it's a first level element + // of the tree + styledString = new StyledString(TextFormatter.crop(TextFormatter.clearLineBreaks(errorMessage), MAX_ERROR_MSG_SIZE)); + } else { + // is used when there is no error message provided in the first + // level element + styledString = new StyledString(NO_ERROR_MESSAGE_PROVIDED); + } + } else { + String[] stackTraceLines = data.getStackTrace().split("\n"); + if (stackTraceLines.length > 0) { + styledString = new StyledString(stackTraceLines[0]); + } else { + styledString = new StyledString(STACK_TRACK_NOT_AVAILABLE); + } + } + return styledString; + case INVOCATION_AFFILLIATION: + if (data instanceof AggregatedExceptionSensorData) { + int percentage = (int) (data.getInvocationAffiliationPercentage() * 100); + int invocations = 0; + if (null != data.getInvocationParentsIdSet()) { + invocations = data.getInvocationParentsIdSet().size(); + } + return TextFormatter.getInvocationAffilliationPercentageString(percentage, invocations); + } + return new StyledString(""); + case CREATED: + if (data instanceof AggregatedExceptionSensorData) { + if (((AggregatedExceptionSensorData) data).getCreated() >= 0) { + return new StyledString(String.valueOf(((AggregatedExceptionSensorData) data).getCreated())); + } + } + return new StyledString(""); + case RETHROWN: + if (data instanceof AggregatedExceptionSensorData) { + if (((AggregatedExceptionSensorData) data).getPassed() >= 0) { + return new StyledString(String.valueOf(((AggregatedExceptionSensorData) data).getPassed())); + } + } + return new StyledString(""); + case HANDLED: + if (data instanceof AggregatedExceptionSensorData) { + if (((AggregatedExceptionSensorData) data).getHandled() >= 0) { + return new StyledString(String.valueOf(((AggregatedExceptionSensorData) data).getHandled())); + } + } + return new StyledString(""); + default: + return new StyledString("error"); + } + } + + /** + * {@inheritDoc} + */ + public String getReadableString(Object object) { + if (object instanceof AggregatedExceptionSensorData) { + AggregatedExceptionSensorData data = (AggregatedExceptionSensorData) object; + StringBuilder sb = new StringBuilder(); + for (Column column : Column.values()) { + sb.append(getStyledTextForColumn(data, column).toString()); + sb.append('\t'); + } + return sb.toString(); + } + throw new RuntimeException("Could not create the human readable string!"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getColumnValues(Object object) { + if (object instanceof AggregatedExceptionSensorData) { + AggregatedExceptionSensorData data = (AggregatedExceptionSensorData) object; + List values = new ArrayList(); + for (Column column : Column.values()) { + values.add(getStyledTextForColumn(data, column).toString()); + } + return values; + } + throw new RuntimeException("Could not create the column values!"); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public Object[] getObjectsToSearch(Object treeInput) { + List allObjects = new ArrayList(); + List exceptionSensorDataList = (List) treeInput; + for (AggregatedExceptionSensorData exData : exceptionSensorDataList) { + allObjects.add(exData); + List messageObjects = parentChildrenMap.get(exData); + if (null != messageObjects) { + allObjects.addAll(messageObjects); + } + } + return allObjects.toArray(); + } + + /** + * {@inheritDoc} + */ + @Override + public SubViewClassification getSubViewClassification() { + return SubViewClassification.SLAVE; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/ExceptionTreeInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/ExceptionTreeInputController.java new file mode 100644 index 000000000..8c7a2cce0 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/ExceptionTreeInputController.java @@ -0,0 +1,422 @@ +package info.novatec.inspectit.rcp.editor.tree.input; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.service.ICachedDataService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.model.ExceptionImageFactory; +import info.novatec.inspectit.rcp.model.ModifiersImageFactory; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.TreeViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Image; + +/** + * This input controller displays the detail contents of {@link ExceptionSensorData} objects. + * + * @author Eduard Tudenhoefner + * + */ +public class ExceptionTreeInputController extends AbstractTreeInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.tree.exceptiontree"; + + /** + * The list of {@link ExceptionSensorData} objects which is displayed. + */ + private List exceptionSensorData = new ArrayList(); + + /** + * The resource manager is used for the images etc. + */ + private LocalResourceManager resourceManager = new LocalResourceManager(JFaceResources.getResources()); + + /** + * The private inner enumeration used to define the used IDs which are mapped into the columns. + * The order in this enumeration represents the order of the columns. If it is reordered, + * nothing else has to be changed. + * + * @author Eduard Tudenhoefner + * + */ + private static enum Column { + /** The event type column. */ + EVENT_TYPE("Event Type", 280, null), + /** The method column. */ + METHOD_CONSTRUCTOR("Method / Constructor", 500, InspectITImages.IMG_METHOD), + /** The error message column. */ + ERROR_MESSAGE("Error Message", 250, null), + /** The cause column. */ + CAUSE("Cause", 120, null); + + /** The name. */ + private String name; + /** The width of the column. */ + private int width; + /** The image descriptor. Can be null */ + private Image image; + + /** + * Default constructor which creates a column enumeration object. + * + * @param name + * The name of the column. + * @param width + * The width of the column. + * @param imageName + * The name of the image. Names are defined in {@link InspectITImages}. + */ + private Column(String name, int width, String imageName) { + this.name = name; + this.width = width; + this.image = InspectIT.getDefault().getImage(imageName); + } + + /** + * Converts an ordinal into a column. + * + * @param i + * The ordinal. + * @return The appropriate column. + */ + public static Column fromOrd(int i) { + if (i < 0 || i >= Column.values().length) { + throw new IndexOutOfBoundsException("Invalid ordinal"); + } + return Column.values()[i]; + } + } + + /** + * The cached service is needed because of the ID mappings. + */ + private ICachedDataService cachedDataService; + + /** + * {@inheritDoc} + */ + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + cachedDataService = inputDefinition.getRepositoryDefinition().getCachedDataService(); + } + + /** + * {@inheritDoc} + */ + public int getExpandLevel() { + return TreeViewer.ALL_LEVELS; + } + + /** + * {@inheritDoc} + */ + public void createColumns(TreeViewer treeViewer) { + for (Column column : Column.values()) { + TreeViewerColumn viewerColumn = new TreeViewerColumn(treeViewer, SWT.NONE); + viewerColumn.getColumn().setMoveable(true); + viewerColumn.getColumn().setResizable(true); + viewerColumn.getColumn().setText(column.name); + viewerColumn.getColumn().setWidth(column.width); + if (null != column.image) { + viewerColumn.getColumn().setImage(column.image); + } + } + } + + /** + * {@inheritDoc} + */ + public Object getTreeInput() { + return exceptionSensorData; + } + + /** + * {@inheritDoc} + */ + public IContentProvider getContentProvider() { + return new ExceptionTreeContentProvider(); + } + + /** + * {@inheritDoc} + */ + public IBaseLabelProvider getLabelProvider() { + return new ExceptionTreeLabelProvider(); + } + + /** + * {@inheritDoc} + */ + public boolean canOpenInput(List data) { + if (null == data) { + return false; + } + + if (data.isEmpty()) { + return true; + } + + if (!(data.get(0) instanceof ExceptionSensorData)) { + return false; + } + + return true; + } + + /** + * The exception tree details label provider for this view. + * + * @author Eduard Tudenhoefner + * + */ + private final class ExceptionTreeLabelProvider extends StyledCellIndexLabelProvider { + + /** + * Creates the styled text. + * + * @param element + * The element to create the styled text for. + * @param index + * The index in the column. + * @return The created styled string. + */ + @Override + public StyledString getStyledText(Object element, int index) { + ExceptionSensorData data = (ExceptionSensorData) element; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + Column enumId = Column.fromOrd(index); + + return getStyledTextForColumn(data, methodIdent, enumId); + } + + /** + * Returns the column image for the given element at the given index. + * + * @param element + * The element. + * @param index + * The index. + * @return Returns the Image. + */ + @Override + public Image getColumnImage(Object element, int index) { + ExceptionSensorData data = (ExceptionSensorData) element; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + Column enumId = Column.fromOrd(index); + + switch (enumId) { + case METHOD_CONSTRUCTOR: + Image image = ModifiersImageFactory.getImage(methodIdent.getModifiers()); + image = ExceptionImageFactory.decorateImageWithException(image, data, resourceManager); + return image; + case EVENT_TYPE: + return null; + case ERROR_MESSAGE: + return null; + case CAUSE: + return null; + default: + return null; + } + } + + /** + * {@inheritDoc} + */ + @Override + protected Color getBackground(Object element, int index) { + return null; + } + } + + /** + * Returns the styled text for a specific column. + * + * @param data + * The data object to extract the information from. + * @param methodIdent + * The method ident object. + * @param enumId + * The enumeration ID. + * @return The styled string containing the information from the data object. + */ + private static StyledString getStyledTextForColumn(ExceptionSensorData data, MethodIdent methodIdent, Column enumId) { + StyledString styledString = null; + switch (enumId) { + case METHOD_CONSTRUCTOR: + return new StyledString(TextFormatter.getMethodWithParameters(methodIdent)); + case EVENT_TYPE: + styledString = new StyledString(data.getExceptionEvent().toString()); + return styledString; + case ERROR_MESSAGE: + styledString = new StyledString(); + if (null != data.getErrorMessage()) { + styledString.append(data.getErrorMessage()); + } + return styledString; + case CAUSE: + styledString = new StyledString(); + if (null != data.getCause()) { + styledString.append(data.getCause().toString()); + } + return styledString; + default: + return styledString; + } + } + + /** + * The exception tree details content provider for this view. + * + * @author Eduard Tudenhoefner + * + */ + private static final class ExceptionTreeContentProvider implements ITreeContentProvider { + + /** + * {@inheritDoc} + */ + public Object[] getChildren(Object parent) { + ExceptionSensorData exceptionSensorData = (ExceptionSensorData) parent; + List exceptionSensorDataList = new ArrayList(); + exceptionSensorDataList.add(exceptionSensorData.getChild()); + + return exceptionSensorDataList.toArray(); + } + + /** + * {@inheritDoc} + */ + public Object getParent(Object child) { + return null; + } + + /** + * {@inheritDoc} + */ + public boolean hasChildren(Object parent) { + if (parent == null) { + return false; + } + + if (parent instanceof ExceptionSensorData) { + ExceptionSensorData exceptionSensorData = (ExceptionSensorData) parent; + if (null != exceptionSensorData.getChild()) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public Object[] getElements(Object inputElement) { + List exceptionSensorData = (List) inputElement; + return exceptionSensorData.toArray(); + } + + /** + * {@inheritDoc} + */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + } + + /** + * {@inheritDoc} + */ + public String getReadableString(Object object) { + if (object instanceof ExceptionSensorData) { + ExceptionSensorData data = (ExceptionSensorData) object; + StringBuilder sb = new StringBuilder(); + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + for (Column column : Column.values()) { + sb.append(getStyledTextForColumn(data, methodIdent, column).toString()); + sb.append('\t'); + } + return sb.toString(); + } + throw new RuntimeException("Could not create the human readable string!"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getColumnValues(Object object) { + if (object instanceof ExceptionSensorData) { + ExceptionSensorData data = (ExceptionSensorData) object; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + List values = new ArrayList(); + for (Column column : Column.values()) { + values.add(getStyledTextForColumn(data, methodIdent, column).toString()); + } + return values; + } + throw new RuntimeException("Could not create the column values!"); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public Object[] getObjectsToSearch(Object treeInput) { + List allObjects = new ArrayList(); + List exceptionSensorDataList = (List) treeInput; + for (ExceptionSensorData exData : exceptionSensorDataList) { + ExceptionSensorData objectToAdd = exData; + while (null != objectToAdd) { + allObjects.add(objectToAdd); + objectToAdd = objectToAdd.getChild(); + } + } + return allObjects.toArray(); + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + resourceManager.dispose(); + } + + /** + * {@inheritDoc} + */ + @Override + public SubViewClassification getSubViewClassification() { + return SubViewClassification.SLAVE; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/InvocDetailInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/InvocDetailInputController.java new file mode 100644 index 000000000..e061c8660 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/InvocDetailInputController.java @@ -0,0 +1,743 @@ +package info.novatec.inspectit.rcp.editor.tree.input; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.service.ICachedDataService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.InvocationSequenceDataHelper; +import info.novatec.inspectit.communication.data.ParameterContentData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.model.ExceptionImageFactory; +import info.novatec.inspectit.rcp.model.ModifiersImageFactory; +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; +import info.novatec.inspectit.rcp.preferences.PreferencesUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.viewers.AbstractTreeViewer; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.TreeViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.ui.progress.DeferredTreeContentManager; + +/** + * This input controller displays the detail contents of {@link InvocationSequenceData} objects. + * + * @author Patrice Bouillet + * + */ +public class InvocDetailInputController extends AbstractTreeInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.tree.invocdetail"; + + /** + * The total duration of the displayed invocation. + */ + private double invocationDuration = 0.0d; + + /** + * The resource manager is used for the images etc. + */ + private LocalResourceManager resourceManager = new LocalResourceManager(JFaceResources.getResources()); + + /** + * The value of the selected data types. + */ + private Set> selectedDataTypes = PreferencesUtils.getObject(PreferencesConstants.INVOCATION_FILTER_DATA_TYPES); + + /** + * The value for the exclusive time filter. + */ + private double defaultExclusiveFilterTime = PreferencesUtils.getDoubleValue(PreferencesConstants.INVOCATION_FILTER_EXCLUSIVE_TIME); + + /** + * The value for the total time filter. + */ + private double defaultTotalFilterTime = PreferencesUtils.getDoubleValue(PreferencesConstants.INVOCATION_FILTER_TOTAL_TIME); + + /** + * The private inner enumeration used to define the used IDs which are mapped into the columns. + * The order in this enumeration represents the order of the columns. If it is reordered, + * nothing else has to be changed. + * + * @author Patrice Bouillet + * + */ + private static enum Column { + /** The method column. */ + METHOD("Method", 700, InspectITImages.IMG_CALL_HIERARCHY), + /** The duration column. */ + DURATION("Duration (ms)", 100, InspectITImages.IMG_TIME), + /** The exclusive duration column. */ + EXCLUSIVE("Exc. duration (ms)", 100, null), + /** The cpu duration column. */ + CPUDURATION("Cpu Duration (ms)", 100, null), + /** The time-stamp column. **/ + START_DELTA("Start Delta (ms)", 100, InspectITImages.IMG_TIME_DELTA), + /** The count column. */ + SQL("SQL", 300, InspectITImages.IMG_DATABASE), + /** The parameter/field contents. */ + PARAMETER("Parameter Content", 200, null); + + /** The name. */ + private String name; + /** The width of the column. */ + private int width; + /** The image descriptor. Can be null */ + private Image image; + + /** + * Default constructor which creates a column enumeration object. + * + * @param name + * The name of the column. + * @param width + * The width of the column. + * @param imageName + * The name of the image. Names are defined in {@link InspectITImages}. + */ + private Column(String name, int width, String imageName) { + this.name = name; + this.width = width; + this.image = InspectIT.getDefault().getImage(imageName); + } + + /** + * Converts an ordinal into a column. + * + * @param i + * The ordinal. + * @return The appropriate column. + */ + public static Column fromOrd(int i) { + if (i < 0 || i >= Column.values().length) { + throw new IndexOutOfBoundsException("Invalid ordinal"); + } + return Column.values()[i]; + } + } + + /** + * The cached service is needed because of the ID mappings. + */ + private ICachedDataService cachedDataService; + + /** + * Current input of the tree. + */ + private Object input; + + /** + * {@inheritDoc} + */ + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + cachedDataService = inputDefinition.getRepositoryDefinition().getCachedDataService(); + } + + /** + * {@inheritDoc} + */ + public void createColumns(TreeViewer treeViewer) { + for (Column column : Column.values()) { + TreeViewerColumn viewerColumn = new TreeViewerColumn(treeViewer, SWT.NONE); + viewerColumn.getColumn().setMoveable(true); + viewerColumn.getColumn().setResizable(true); + viewerColumn.getColumn().setText(column.name); + viewerColumn.getColumn().setWidth(column.width); + if (null != column.image) { + viewerColumn.getColumn().setImage(column.image); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public Object getTreeInput() { + return input; + } + + /** + * {@inheritDoc} + */ + public IContentProvider getContentProvider() { + return new InvocDetailContentProvider(); + } + + /** + * {@inheritDoc} + */ + public IBaseLabelProvider getLabelProvider() { + return new InvocDetailLabelProvider(); + } + + /** + * {@inheritDoc} + */ + public Set getPreferenceIds() { + Set preferences = EnumSet.noneOf(PreferenceId.class); + preferences.add(PreferenceId.FILTERDATATYPE); + preferences.add(PreferenceId.INVOCFILTEREXCLUSIVETIME); + preferences.add(PreferenceId.INVOCFILTERTOTALTIME); + return preferences; + } + + /** + * {@inheritDoc} + */ + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + switch (preferenceEvent.getPreferenceId()) { + case FILTERDATATYPE: + Class dataTypeClass = (Class) preferenceEvent.getPreferenceMap().get(PreferenceId.DataTypeSelection.SENSOR_DATA_SELECTION_ID); + if (selectedDataTypes.contains(dataTypeClass)) { + selectedDataTypes.remove(dataTypeClass); + } else { + selectedDataTypes.add(dataTypeClass); + } + break; + case INVOCFILTEREXCLUSIVETIME: + defaultExclusiveFilterTime = (Double) preferenceEvent.getPreferenceMap().get(PreferenceId.InvocExclusiveTimeSelection.TIME_SELECTION_ID); + break; + case INVOCFILTERTOTALTIME: + defaultTotalFilterTime = (Double) preferenceEvent.getPreferenceMap().get(PreferenceId.InvocTotalTimeSelection.TIME_SELECTION_ID); + break; + default: + // nothing to do by default + break; + } + } + + /** + * {@inheritDoc} + */ + public boolean canOpenInput(List data) { + if (null == data) { + return false; + } + + if (data.isEmpty()) { + return true; + } + + if (data.size() != 1) { + return false; + } + + if (!(data.get(0) instanceof InvocationSequenceData)) { + return false; + } + + // we are saving the complete duration of this invocation sequence + invocationDuration = ((InvocationSequenceData) data.get(0)).getDuration(); + + return true; + } + + /** + * The invoc detail label provider for this view. + * + * @author Patrice Bouillet + * + */ + private final class InvocDetailLabelProvider extends StyledCellIndexLabelProvider { + + /** + * Creates the styled text. + * + * @param element + * The element to create the styled text for. + * @param index + * The index in the column. + * @return The created styled string. + */ + @Override + public StyledString getStyledText(Object element, int index) { + InvocationSequenceData data = (InvocationSequenceData) element; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + Column enumId = Column.fromOrd(index); + + return getStyledTextForColumn(data, methodIdent, enumId); + } + + /** + * Returns the column image for the given element at the given index. + * + * @param element + * The element. + * @param index + * The index. + * @return Returns the Image. + */ + @Override + public Image getColumnImage(Object element, int index) { + InvocationSequenceData data = (InvocationSequenceData) element; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + Column enumId = Column.fromOrd(index); + + switch (enumId) { + case METHOD: + ExceptionSensorData exceptionSensorData = null; + Image image = ModifiersImageFactory.getImage(methodIdent.getModifiers()); + + if (InvocationSequenceDataHelper.hasExceptionData(data)) { + exceptionSensorData = data.getExceptionSensorDataObjects().get(data.getExceptionSensorDataObjects().size() - 1); + image = ExceptionImageFactory.decorateImageWithException(image, exceptionSensorData, resourceManager); + } + + return image; + case DURATION: + return null; + case CPUDURATION: + return null; + case EXCLUSIVE: + return null; + case SQL: + return null; + case PARAMETER: + return null; + default: + return null; + } + } + + /** + * {@inheritDoc} + */ + @Override + protected Color getBackground(Object element, int index) { + InvocationSequenceData data = (InvocationSequenceData) element; + double duration = InvocationSequenceDataHelper.calculateDuration(data); + + if (-1.0d != duration) { // no duration? + double exclusiveTime = duration - InvocationSequenceDataHelper.computeNestedDuration(data); + + // compute the correct color + int colorValue = 255 - (int) ((exclusiveTime / invocationDuration) * 100); + + if (colorValue > 255 || colorValue < 0) { + InspectIT.getDefault().createErrorDialog("The computation of the color value for the detail view returned an invalid value: " + colorValue, null, -1); + return null; + } + + Color color = resourceManager.createColor(new RGB(colorValue, colorValue, colorValue)); + return color; + } + + return null; + } + + } + + /** + * Returns the styled text for a specific column. + * + * @param data + * The data object to extract the information from. + * @param methodIdent + * The method ident object. + * @param enumId + * The enumeration ID. + * @return The styled string containing the information from the data object. + */ + private static StyledString getStyledTextForColumn(InvocationSequenceData data, MethodIdent methodIdent, Column enumId) { + StyledString styledString = null; + switch (enumId) { + case METHOD: + return TextFormatter.getStyledMethodString(methodIdent); + case START_DELTA: + InvocationSequenceData root = data; + while (!InvocationSequenceDataHelper.isRootElementInSequence(root)) { + root = root.getParentSequence(); + } + long delta = data.getTimeStamp().getTime() - root.getTimeStamp().getTime(); + return new StyledString(NumberFormatter.formatLong(delta)); + case DURATION: + styledString = new StyledString(); + double duration = InvocationSequenceDataHelper.calculateDuration(data); + if (-1.0d != duration) { + styledString.append(NumberFormatter.formatDouble(duration)); + } + return styledString; + case CPUDURATION: + styledString = new StyledString(); + if (InvocationSequenceDataHelper.hasTimerData(data) && data.getTimerData().isCpuMetricDataAvailable()) { + styledString.append(NumberFormatter.formatDouble(data.getTimerData().getCpuDuration())); + } + return styledString; + case EXCLUSIVE: + styledString = new StyledString(); + double dur = InvocationSequenceDataHelper.calculateDuration(data); + + if (-1.0d != dur) { + double exclusiveTime = dur - InvocationSequenceDataHelper.computeNestedDuration(data); + styledString.append(NumberFormatter.formatDouble(exclusiveTime)); + } + + return styledString; + case SQL: + styledString = new StyledString(); + if (InvocationSequenceDataHelper.hasSQLData(data)) { + styledString.append(TextFormatter.clearLineBreaks(data.getSqlStatementData().getSqlWithParameterValues())); + } + return styledString; + case PARAMETER: + styledString = new StyledString(); + + if (InvocationSequenceDataHelper.hasHttpTimerData(data)) { + HttpTimerData httpTimer = (HttpTimerData) data.getTimerData(); + if (null != httpTimer.getUri()) { + styledString.append("URI: "); + styledString.append(httpTimer.getUri()); + styledString.append(" | "); + } + } + + if (InvocationSequenceDataHelper.hasCapturedParameters(data)) { + Set parameters = InvocationSequenceDataHelper.getCapturedParameters(data); + + boolean isFirst = true; + for (ParameterContentData parameterContentData : parameters) { + // shorten the representation here. + if (!isFirst) { + styledString.append(", "); + } else { + isFirst = false; + } + styledString.append("'"); + styledString.append(parameterContentData.getName()); + styledString.append("': "); + styledString.append(TextFormatter.clearLineBreaks(parameterContentData.getContent())); + } + } + + return styledString; + default: + return styledString; + } + } + + /** + * The invoc detail content provider for this view. + * + * @author Patrice Bouillet + * + */ + private final class InvocDetailContentProvider implements ITreeContentProvider { + + /** + * The deferred manager is used here to update the tree in a concurrent thread so the UI + * responds much better if many items are displayed. + */ + private DeferredTreeContentManager manager; + + /** + * {@inheritDoc} + */ + public Object[] getChildren(Object parent) { + if (manager.isDeferredAdapter(parent)) { + Object[] children = manager.getChildren(parent); + + return children; + } + + return new Object[0]; + } + + /** + * {@inheritDoc} + */ + public Object getParent(Object child) { + if (child instanceof InvocationSequenceData) { + InvocationSequenceData invocationSequenceData = (InvocationSequenceData) child; + return invocationSequenceData.getParentSequence(); + } + + return null; + } + + /** + * {@inheritDoc} + */ + public boolean hasChildren(Object parent) { + if (parent == null) { + return false; + } + + if (parent instanceof InvocationSequenceData) { + InvocationSequenceData invocationSequenceData = (InvocationSequenceData) parent; + if (!invocationSequenceData.getNestedSequences().isEmpty()) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public Object[] getElements(Object inputElement) { + List invocationSequenceData = (List) inputElement; + return invocationSequenceData.toArray(); + } + + /** + * {@inheritDoc} + */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + manager = new DeferredTreeContentManager((AbstractTreeViewer) viewer); + input = newInput; + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + + } + + /** + * {@inheritDoc} + */ + @Override + public ViewerFilter[] getFilters() { + ViewerFilter sensorDataFilter = new InvocationViewerFilter() { + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + if (element instanceof InvocationSequenceData) { + InvocationSequenceData invocationSequenceData = (InvocationSequenceData) element; + if (checkIsOnlyInvocation(invocationSequenceData) && checkSensorDataTypeForObject(invocationSequenceData)) { + return true; + } else if (checkSensorDataTypeForObject(invocationSequenceData.getTimerData())) { + return true; + } else if (checkSensorDataTypeForObject(invocationSequenceData.getSqlStatementData())) { + return true; + } else if (CollectionUtils.isNotEmpty(invocationSequenceData.getExceptionSensorDataObjects())) { + return checkSensorDataTypeForObject(invocationSequenceData.getExceptionSensorDataObjects().get(0)); + } + return false; + } + return true; + } + + private boolean checkSensorDataTypeForObject(Object object) { + if (null != object) { + return selectedDataTypes.contains(object.getClass()); + } + return false; + } + + private boolean checkIsOnlyInvocation(InvocationSequenceData data) { + return null == data.getTimerData() && null == data.getSqlStatementData() && CollectionUtils.isEmpty(data.getExceptionSensorDataObjects()); + } + }; + ViewerFilter exclusiveTimeFilter = new InvocationViewerFilter() { + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + if (Double.isNaN(defaultExclusiveFilterTime)) { + return true; + } + + if (element instanceof InvocationSequenceData) { + InvocationSequenceData invocationSequenceData = (InvocationSequenceData) element; + + // filter by the exclusive duration + double duration = Double.NaN; + if (InvocationSequenceDataHelper.hasSQLData(invocationSequenceData)) { + duration = invocationSequenceData.getSqlStatementData().getDuration(); + } else if (InvocationSequenceDataHelper.hasTimerData(invocationSequenceData)) { + double totalDuration = invocationSequenceData.getTimerData().getDuration(); + duration = totalDuration - InvocationSequenceDataHelper.computeNestedDuration(invocationSequenceData); + } else if (InvocationSequenceDataHelper.isRootElementInSequence(invocationSequenceData)) { + duration = invocationSequenceData.getDuration() - InvocationSequenceDataHelper.computeNestedDuration(invocationSequenceData); + } + + if (!Double.isNaN(duration) && duration <= defaultExclusiveFilterTime) { + return false; + } + } + return true; + } + }; + ViewerFilter totalTimeFilter = new InvocationViewerFilter() { + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + if (Double.isNaN(defaultTotalFilterTime)) { + return true; + } + + if (element instanceof InvocationSequenceData) { + InvocationSequenceData invocationSequenceData = (InvocationSequenceData) element; + + // filter by the exclusive duration + double duration = InvocationSequenceDataHelper.calculateDuration(invocationSequenceData); + if (duration != -1.0d && duration <= defaultTotalFilterTime) { + return false; + } + } + return true; + } + }; + return new ViewerFilter[] { sensorDataFilter, exclusiveTimeFilter, totalTimeFilter }; + } + + /** + * This class is needed to modify the filter method which behaves a little bit differently than + * the original one: Instead of filtering out a specific element _and_ all its sub-elements, it + * only filters out the specific elements and pushes up the elements which are child-elements of + * that one. + * + * @author Patrice Bouillet + * + */ + private abstract class InvocationViewerFilter extends ViewerFilter { + /** + * The filtering method which tries to push up the child elements if a parent element has to + * be filtered out. + * + * @param viewer + * The viewer + * @param parent + * The parent object + * @param elements + * The elements to check if they should be filtered + * @return Returns a set of elements which could be now even more than the initial elements + */ + @Override + public Object[] filter(Viewer viewer, Object parent, Object[] elements) { + List out = new ArrayList(); + for (Object element : elements) { + if (select(viewer, parent, element)) { + out.add(element); + } else { + // This else branch has to be added to not filter out + // child elements which would pass the filter. + if (element instanceof InvocationSequenceData) { + InvocationSequenceData data = (InvocationSequenceData) element; + if (data.getChildCount() > 0) { + // the parent object stays the same as this is the + // graphical representation and not the underlying model + out.addAll(Arrays.asList(filter(viewer, parent, data.getNestedSequences().toArray()))); + } + } + } + } + return out.toArray(); + } + } + + /** + * {@inheritDoc} + */ + public String getReadableString(Object object) { + if (object instanceof InvocationSequenceData) { + InvocationSequenceData data = (InvocationSequenceData) object; + StringBuilder sb = new StringBuilder(); + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + for (Column column : Column.values()) { + sb.append(getStyledTextForColumn(data, methodIdent, column).toString()); + sb.append('\t'); + } + return sb.toString(); + } + throw new RuntimeException("Could not create the human readable string!"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getColumnValues(Object object) { + if (object instanceof InvocationSequenceData) { + InvocationSequenceData data = (InvocationSequenceData) object; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(data.getMethodIdent()); + List values = new ArrayList(); + for (Column column : Column.values()) { + values.add(getStyledTextForColumn(data, methodIdent, column).toString()); + } + return values; + } + throw new RuntimeException("Could not create the column values!"); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public Object[] getObjectsToSearch(Object treeInput) { + List invocationSequenceDataList = (List) treeInput; + if (!invocationSequenceDataList.isEmpty()) { + InvocationSequenceData invocation = invocationSequenceDataList.get(0); + List allObjects = new ArrayList((int) invocation.getChildCount()); + extractAllChildren(allObjects, invocation); + return allObjects.toArray(); + } + return new Object[0]; + + } + + /** + * Extracts all invocations inside the invocation in one list via reflection. + * + * @param resultList + * List to contain all the extracted data. + * @param invocation + * Invocation to extract. + */ + private void extractAllChildren(List resultList, InvocationSequenceData invocation) { + resultList.add(invocation); + for (InvocationSequenceData child : invocation.getNestedSequences()) { + extractAllChildren(resultList, child); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + resourceManager.dispose(); + } + + /** + * {@inheritDoc} + */ + @Override + public SubViewClassification getSubViewClassification() { + return SubViewClassification.SLAVE; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/SqlInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/SqlInputController.java new file mode 100644 index 000000000..0c234c0d2 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/SqlInputController.java @@ -0,0 +1,656 @@ +package info.novatec.inspectit.rcp.editor.tree.input; + +import info.novatec.inspectit.cmr.service.ICachedDataService; +import info.novatec.inspectit.cmr.service.ISqlDataAccessService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.comparator.IDataComparator; +import info.novatec.inspectit.communication.comparator.InvocationAwareDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.communication.comparator.SqlStatementDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.TimerDataComparatorEnum; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.SqlStatementInputDefinitionExtra; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId.LiveMode; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; +import info.novatec.inspectit.rcp.editor.text.input.SqlStatementTextInputController.SqlHolderHelper; +import info.novatec.inspectit.rcp.editor.tree.TreeViewerComparator; +import info.novatec.inspectit.rcp.editor.tree.util.DatabaseSqlTreeComparator; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; +import info.novatec.inspectit.rcp.preferences.PreferencesUtils; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.util.data.DatabaseInfoHelper; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.TreeViewerColumn; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; + +/** + * This input controller displays the contents of {@link SqlStatementData} objects. + * + * @author Patrice Bouillet + * + */ +public class SqlInputController extends AbstractTreeInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.tree.sql"; + + /** + * Empty {@link StyledString}. + */ + private static final StyledString EMPTY_STYLED_STRING = new StyledString(""); + + /** + * The private inner enumeration used to define the used IDs which are mapped into the columns. + * The order in this enumeration represents the order of the columns. If it is reordered, + * nothing else has to be changed. + * + * @author Patrice Bouillet + * + */ + private static enum Column { + + /** The column containing the name of the database. */ + DATABASE_URL("Database URL", 120, null, null), + /** The statement column. */ + STATEMENT("Statement", 600, InspectITImages.IMG_DATABASE, SqlStatementDataComparatorEnum.SQL), + /** Invocation Affiliation. */ + INVOCATION_AFFILLIATION("In Invocations", 120, InspectITImages.IMG_INVOCATION, InvocationAwareDataComparatorEnum.INVOCATION_AFFILIATION), + /** The count column. */ + COUNT("Count", 80, null, TimerDataComparatorEnum.COUNT), + /** The average column. */ + AVERAGE("Avg (ms)", 80, null, TimerDataComparatorEnum.AVERAGE), + /** The min column. */ + MIN("Min (ms)", 80, null, TimerDataComparatorEnum.MIN), + /** The max column. */ + MAX("Max (ms)", 80, null, TimerDataComparatorEnum.MAX), + /** The duration column. */ + DURATION("Duration (ms)", 80, null, TimerDataComparatorEnum.MAX), + /** The prepared column. */ + PREPARED("Prepared?", 80, null, SqlStatementDataComparatorEnum.IS_PREPARED_STATEMENT); + + /** The name. */ + private String name; + /** The width of the column. */ + private int width; + /** The image descriptor. Can be null */ + private Image image; + /** Comparator for the column. */ + private IDataComparator dataComparator; + + /** + * Default constructor which creates a column enumeration object. + * + * @param name + * The name of the column. + * @param width + * The width of the column. + * @param imageName + * The name of the image. Names are defined in {@link InspectITImages}. + * @param dataComparator + * Comparator for the column. + */ + private Column(String name, int width, String imageName, IDataComparator dataComparator) { + this.name = name; + this.width = width; + this.image = InspectIT.getDefault().getImage(imageName); + this.dataComparator = dataComparator; + } + + /** + * Converts an ordinal into a column. + * + * @param i + * The ordinal. + * @return The appropriate column. + */ + public static Column fromOrd(int i) { + if (i < 0 || i >= Column.values().length) { + throw new IndexOutOfBoundsException("Invalid ordinal"); + } + return Column.values()[i]; + } + + } + + /** + * The template which is send to the Repository to retrieve the actual data. + */ + private SqlStatementData template; + + /** + * Input map. + */ + private Map> inputMap = new HashMap<>(); + + /** + * The data access service to access the data on the CMR. + */ + private ISqlDataAccessService dataAccessService; + + /** + * The cached service is needed because of the ID mappings. + */ + private ICachedDataService cachedDataService; + + /** + * Date to display invocations from. + */ + private Date fromDate = null; + + /** + * Date to display invocations to. + */ + private Date toDate = null; + + /** + * Are we in live mode. + */ + private boolean autoUpdate = LiveMode.ACTIVE_DEFAULT; + + /** + * Decimal places. + */ + private int timeDecimalPlaces = PreferencesUtils.getIntValue(PreferencesConstants.DECIMAL_PLACES); + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + template = new SqlStatementData(); + template.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); + template.setId(-1); + + if (inputDefinition.hasInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.SQL_STATEMENT_EXTRAS_MARKER)) { + SqlStatementInputDefinitionExtra inputDefinitionExtra = inputDefinition.getInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.SQL_STATEMENT_EXTRAS_MARKER); + template.setSql(inputDefinitionExtra.getSql()); + } + + dataAccessService = inputDefinition.getRepositoryDefinition().getSqlDataAccessService(); + cachedDataService = inputDefinition.getRepositoryDefinition().getCachedDataService(); + } + + /** + * {@inheritDoc} + */ + public void createColumns(TreeViewer treeViewer) { + for (Column column : Column.values()) { + TreeViewerColumn viewerColumn = new TreeViewerColumn(treeViewer, SWT.NONE); + viewerColumn.getColumn().setMoveable(true); + viewerColumn.getColumn().setResizable(true); + viewerColumn.getColumn().setText(column.name); + viewerColumn.getColumn().setWidth(column.width); + if (null != column.image) { + viewerColumn.getColumn().setImage(column.image); + } + mapTreeViewerColumn(column, viewerColumn); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Object getTreeInput() { + return inputMap.keySet(); + } + + /** + * {@inheritDoc} + */ + public IContentProvider getContentProvider() { + return new SqlContentProvider(); + } + + /** + * {@inheritDoc} + */ + public IBaseLabelProvider getLabelProvider() { + return new SqlLabelProvider(); + } + + /** + * {@inheritDoc} + */ + public ViewerComparator getComparator() { + TreeViewerComparator sqlViewerComparator = new DatabaseSqlTreeComparator(); + for (Column column : Column.values()) { + if (null != column.dataComparator) { + ResultComparator resultComparator = new ResultComparator(column.dataComparator, cachedDataService); + sqlViewerComparator.addColumn(getMappedTreeViewerColumn(column).getColumn(), resultComparator); + } + } + + return sqlViewerComparator; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getPreferenceIds() { + Set preferences = EnumSet.noneOf(PreferenceId.class); + if (getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) { + preferences.add(PreferenceId.CLEAR_BUFFER); + preferences.add(PreferenceId.LIVEMODE); + } + preferences.add(PreferenceId.UPDATE); + preferences.add(PreferenceId.TIME_RESOLUTION); + preferences.add(PreferenceId.TIMELINE); + return preferences; + } + + /** + * {@inheritDoc} + */ + @Override + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + switch (preferenceEvent.getPreferenceId()) { + case TIMELINE: + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeLine.FROM_DATE_ID)) { + fromDate = (Date) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeLine.FROM_DATE_ID); + } + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeLine.TO_DATE_ID)) { + toDate = (Date) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeLine.TO_DATE_ID); + } + break; + case LIVEMODE: + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.LiveMode.BUTTON_LIVE_ID)) { + autoUpdate = (Boolean) preferenceEvent.getPreferenceMap().get(PreferenceId.LiveMode.BUTTON_LIVE_ID); + } + break; + case TIME_RESOLUTION: + if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeResolution.TIME_DECIMAL_PLACES_ID)) { + timeDecimalPlaces = (Integer) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeResolution.TIME_DECIMAL_PLACES_ID); + } + break; + default: + break; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canOpenInput(List data) { + if (data != null) { + for (DefaultData defaultData : data) { + if (!(defaultData instanceof SqlStatementData)) { + return false; + } + } + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void doRefresh(IProgressMonitor monitor, final IRootEditor rootEditor) { + monitor.beginTask("Getting SQL information", IProgressMonitor.UNKNOWN); + List sqlStatementList; + if (autoUpdate) { + sqlStatementList = dataAccessService.getAggregatedSqlStatements(template); + } else { + sqlStatementList = dataAccessService.getAggregatedSqlStatements(template, fromDate, toDate); + } + + inputMap.clear(); + if (CollectionUtils.isNotEmpty(sqlStatementList)) { + inputMap.putAll(createInputMap(sqlStatementList)); + } + + Display.getDefault().asyncExec(new Runnable() { + public void run() { + if (null != rootEditor) { + rootEditor.setDataInput(Collections. emptyList()); + } + } + }); + monitor.done(); + } + + /** + * Create input map from list of {@link SqlStatementData}s. + * + * @param sqlStatementDatas + * {@link SqlStatementData}s + * @return Input map + */ + private Map> createInputMap(List sqlStatementDatas) { + Map> map = new HashMap<>(); + for (SqlStatementData sqlStatementData : sqlStatementDatas) { + DatabaseInfoHelper helper = new DatabaseInfoHelper(sqlStatementData); + List list = map.get(helper); + if (null == list) { + list = new ArrayList<>(); + map.put(helper, list); + } + list.add(sqlStatementData); + } + return map; + } + + /** + * The sql label provider used by this view. + * + * @author Patrice Bouillet + * + */ + private final class SqlLabelProvider extends StyledCellIndexLabelProvider { + + /** + * Creates the styled text. + * + * @param element + * The element to create the styled text for. + * @param index + * The index in the column. + * @return The created styled string. + */ + @Override + public StyledString getStyledText(Object element, int index) { + Column enumId = Column.fromOrd(index); + + if (element instanceof SqlStatementData) { + return getSqlStyledTextForColumn((SqlStatementData) element, enumId); + } else if (element instanceof DatabaseInfoHelper) { + return getDatabaseStyledTextForColumn((DatabaseInfoHelper) element, enumId); + } + return EMPTY_STYLED_STRING; + } + + /** + * {@inheritDoc} + */ + @Override + public String getToolTipText(Object element, int index) { + if (element instanceof DatabaseInfoHelper) { + DatabaseInfoHelper helper = (DatabaseInfoHelper) element; + return helper.getLongText(); + } + return super.getToolTipText(element, index); + } + } + + /** + * The sql content provider used by this view. + * + * @author Patrice Bouillet + * + */ + private final class SqlContentProvider extends ArrayContentProvider implements ITreeContentProvider { + + /** + * {@inheritDoc} + */ + @Override + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof DatabaseInfoHelper) { + return inputMap.get(parentElement).toArray(); + } + return new Object[0]; + } + + /** + * {@inheritDoc} + */ + @Override + public Object getParent(Object element) { + if (element instanceof SqlStatementData) { + return new DatabaseInfoHelper((SqlStatementData) element); + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasChildren(Object element) { + return element instanceof DatabaseInfoHelper; + } + + } + + /** + * {@inheritDoc} + */ + @Override + public void doubleClick(DoubleClickEvent event) { + final StructuredSelection selection = (StructuredSelection) event.getSelection(); + if (!selection.isEmpty() && selection.getFirstElement() instanceof SqlStatementData) { + try { + PlatformUI.getWorkbench().getProgressService().busyCursorWhile(new IRunnableWithProgress() { + public void run(final IProgressMonitor monitor) { + monitor.beginTask("Retrieving Parameter Aggregated SQLs", IProgressMonitor.UNKNOWN); + SqlStatementData data = (SqlStatementData) selection.getFirstElement(); + List dataList = Collections.emptyList(); + boolean hasNoParameters = !data.isPreparedStatement(); + if (data.isPreparedStatement()) { + dataList = dataAccessService.getParameterAggregatedSqlStatements(data, fromDate, toDate); + + // if we have only one statement and it has no parameters, we won't load + // the bottom part with empty parameters + if (dataList.size() == 1 && CollectionUtils.isEmpty(dataList.get(0).getParameterValues())) { + hasNoParameters = true; + } + } + + if (hasNoParameters) { + final SqlHolderHelper inputForParametersTable = new SqlHolderHelper(Collections. emptyList(), true); + final SqlHolderHelper inputForTextOnly = new SqlHolderHelper(Collections.singletonList(data), false); + Display.getDefault().asyncExec(new Runnable() { + public void run() { + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + IWorkbenchPage page = window.getActivePage(); + IRootEditor rootEditor = (IRootEditor) page.getActiveEditor(); + if (null != rootEditor) { + rootEditor.setDataInput(Collections.singletonList(inputForParametersTable)); + rootEditor.setDataInput(Collections.singletonList(inputForTextOnly)); + } + } + }); + + } else { + final SqlHolderHelper inputForParametersTable = new SqlHolderHelper(dataList, true); + Display.getDefault().asyncExec(new Runnable() { + public void run() { + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + IWorkbenchPage page = window.getActivePage(); + IRootEditor rootEditor = (IRootEditor) page.getActiveEditor(); + if (null != rootEditor) { + rootEditor.setDataInput(Collections.singletonList(inputForParametersTable)); + } + } + }); + } + monitor.done(); + } + }); + } catch (InvocationTargetException e) { + MessageDialog.openError(Display.getDefault().getActiveShell().getShell(), "Error", e.getCause().toString()); + } catch (InterruptedException e) { + MessageDialog.openInformation(Display.getDefault().getActiveShell().getShell(), "Cancelled", e.getCause().toString()); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public int getExpandLevel() { + return TreeViewer.ALL_LEVELS; + } + + /** + * Returns the styled text for a specific column. + * + * @param data + * The data object to extract the information from. + * @param enumId + * The enumeration ID. + * @return The styled string containing the information from the data object. + */ + private StyledString getSqlStyledTextForColumn(SqlStatementData data, Column enumId) { + switch (enumId) { + case STATEMENT: + String sql = data.getSql().replaceAll("[\r\n]+", " "); + return new StyledString(sql); + case INVOCATION_AFFILLIATION: + int percentage = (int) (data.getInvocationAffiliationPercentage() * 100); + int invocations = 0; + if (null != data.getInvocationParentsIdSet()) { + invocations = data.getInvocationParentsIdSet().size(); + } + return TextFormatter.getInvocationAffilliationPercentageString(percentage, invocations); + case COUNT: + return new StyledString(Long.toString(data.getCount())); + case AVERAGE: + return new StyledString(NumberFormatter.formatDouble(data.getAverage(), timeDecimalPlaces)); + case MIN: + return new StyledString(NumberFormatter.formatDouble(data.getMin(), timeDecimalPlaces)); + case MAX: + return new StyledString(NumberFormatter.formatDouble(data.getMax(), timeDecimalPlaces)); + case DURATION: + return new StyledString(NumberFormatter.formatDouble(data.getDuration(), timeDecimalPlaces)); + case PREPARED: + if (data.isPreparedStatement()) { + return new StyledString("true"); + } else { + return new StyledString("false"); + } + default: + return EMPTY_STYLED_STRING; + } + } + + /** + * Returns the styled text for a specific column. + * + * @param data + * {@link DatabaseInfoHelper}. + * @param enumId + * The enumeration ID. + * @return The styled string containing the information from the data object. + */ + private StyledString getDatabaseStyledTextForColumn(DatabaseInfoHelper data, Column enumId) { + switch (enumId) { + case DATABASE_URL: + return new StyledString(data.getDatabaseUrl()); + default: + return EMPTY_STYLED_STRING; + } + } + + /** + * {@inheritDoc} + */ + public String getReadableString(Object object) { + if (object instanceof SqlStatementData) { + SqlStatementData data = (SqlStatementData) object; + StringBuilder sb = new StringBuilder(); + for (Column column : Column.values()) { + sb.append(getSqlStyledTextForColumn(data, column).toString()); + sb.append('\t'); + } + return sb.toString(); + } else if (object instanceof DatabaseInfoHelper) { + DatabaseInfoHelper data = (DatabaseInfoHelper) object; + StringBuilder sb = new StringBuilder(); + for (Column column : Column.values()) { + sb.append(getDatabaseStyledTextForColumn(data, column).toString()); + sb.append('\t'); + } + return sb.toString(); + } + throw new RuntimeException("Could not create the human readable string!"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getColumnValues(Object object) { + if (object instanceof SqlStatementData) { + SqlStatementData data = (SqlStatementData) object; + List values = new ArrayList(); + for (Column column : Column.values()) { + values.add(getSqlStyledTextForColumn(data, column).toString()); + } + return values; + } else if (object instanceof DatabaseInfoHelper) { + DatabaseInfoHelper data = (DatabaseInfoHelper) object; + List values = new ArrayList(); + for (Column column : Column.values()) { + values.add(getDatabaseStyledTextForColumn(data, column).toString()); + } + return values; + } + throw new RuntimeException("Could not create the column values!"); + } + + /** + * {@inheritDoc} + */ + @Override + public Object[] getObjectsToSearch(Object treeInput) { + List sqlStatementDatas = new ArrayList<>(); + for (List datas : inputMap.values()) { + sqlStatementDatas.addAll(datas); + } + return sqlStatementDatas.toArray(); + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + inputMap.clear(); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/SqlInvocInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/SqlInvocInputController.java new file mode 100644 index 000000000..1cc7917ba --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/SqlInvocInputController.java @@ -0,0 +1,657 @@ +package info.novatec.inspectit.rcp.editor.tree.input; + +import info.novatec.inspectit.cmr.service.ICachedDataService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.comparator.DefaultDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.IDataComparator; +import info.novatec.inspectit.communication.comparator.SqlStatementDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.TimerDataComparatorEnum; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.indexing.aggregation.impl.AggregationPerformer; +import info.novatec.inspectit.indexing.aggregation.impl.SqlStatementDataAggregator; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.IPreferenceGroup; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.tree.TreeViewerComparator; +import info.novatec.inspectit.rcp.editor.tree.util.DatabaseSqlTreeComparator; +import info.novatec.inspectit.rcp.editor.viewers.RawAggregatedResultComparator; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.handlers.ShowHideColumnsHandler; +import info.novatec.inspectit.rcp.util.data.DatabaseInfoHelper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.TreeViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.TreeColumn; + +/** + * This input controller displays the contents of {@link SqlStatementData} objects in an invocation + * sequence. + * + * @author Patrice Bouillet + * + */ +public class SqlInvocInputController extends AbstractTreeInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.tree.sqlinvoc"; + + /** + * The private inner enumeration used to define the used IDs which are mapped into the columns. + * The order in this enumeration represents the order of the columns. If it is reordered, + * nothing else has to be changed. + * + * @author Patrice Bouillet + * + */ + private static enum Column { + /** The timestamp column. */ + TIMESTAMP("Timestamp", 130, InspectITImages.IMG_TIMESTAMP, false, true, DefaultDataComparatorEnum.TIMESTAMP), + /** The column containing the name of the database. */ + DATABASE_URL("Database URL", 120, null, true, true, null), + /** The statement column. */ + STATEMENT("Statement", 600, InspectITImages.IMG_DATABASE, true, true, SqlStatementDataComparatorEnum.SQL_AND_PARAMETERS), + /** The count column. */ + COUNT("Count", 80, null, true, false, TimerDataComparatorEnum.COUNT), + /** The average column. */ + AVERAGE("Avg (ms)", 80, null, true, false, TimerDataComparatorEnum.AVERAGE), + /** The min column. */ + MIN("Min (ms)", 80, null, true, false, TimerDataComparatorEnum.MIN), + /** The max column. */ + MAX("Max (ms)", 80, null, true, false, TimerDataComparatorEnum.MAX), + /** The duration column. */ + DURATION("Duration (ms)", 80, null, true, true, TimerDataComparatorEnum.DURATION), + /** The prepared column. */ + PREPARED("Prepared?", 60, null, false, true, SqlStatementDataComparatorEnum.IS_PREPARED_STATEMENT); + + /** The name. */ + private String name; + /** The width of the column. */ + private int width; + /** The image descriptor. Can be null */ + private Image image; + /** If the column should be shown in aggregated mode. */ + private boolean showInAggregatedMode; + /** If the column should be shown in raw mode. */ + private boolean showInRawMode; + /** Comparator for the column. */ + private IDataComparator dataComparator; + + /** + * Default constructor which creates a column enumeration object. + * + * @param name + * The name of the column. + * @param width + * The width of the column. + * @param imageName + * The name of the image. Names are defined in {@link InspectITImages}. + * @param showInAggregatedMode + * If the column should be shown in aggregated mode. + * @param showInRawMode + * If the column should be shown in raw mode. + * @param dataComparator + * Comparator for the column. + * + */ + private Column(String name, int width, String imageName, boolean showInAggregatedMode, boolean showInRawMode, IDataComparator dataComparator) { + this.name = name; + this.width = width; + this.image = InspectIT.getDefault().getImage(imageName); + this.showInAggregatedMode = showInAggregatedMode; + this.showInRawMode = showInRawMode; + this.dataComparator = dataComparator; + } + + /** + * Converts an ordinal into a column. + * + * @param i + * The ordinal. + * @return The appropriate column. + */ + public static Column fromOrd(int i) { + if (i < 0 || i >= Column.values().length) { + throw new IndexOutOfBoundsException("Invalid ordinal"); + } + return Column.values()[i]; + } + } + + /** + * The cached service is needed because of the ID mappings. + */ + private ICachedDataService cachedDataService; + + /** + * List that is displayed after processing the invocation. + */ + private List sqlStatementDataList; + + /** + * Map of SQls and the database they belong to. + */ + private Map> databaseSqlMap; + + /** + * Empty styled string. + */ + private final StyledString emptyStyledString = new StyledString(); + + /** + * Should view display raw mode or not. + */ + private boolean rawMode = false; + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + + cachedDataService = inputDefinition.getRepositoryDefinition().getCachedDataService(); + } + + /** + * {@inheritDoc} + */ + public void createColumns(TreeViewer treeViewer) { + for (Column column : Column.values()) { + TreeViewerColumn viewerColumn = new TreeViewerColumn(treeViewer, SWT.NONE); + viewerColumn.getColumn().setMoveable(true); + viewerColumn.getColumn().setResizable(true); + viewerColumn.getColumn().setText(column.name); + if (column.showInAggregatedMode) { + viewerColumn.getColumn().setWidth(column.width); + } else { + viewerColumn.getColumn().setWidth(0); + } + if (null != column.image) { + viewerColumn.getColumn().setImage(column.image); + } + mapTreeViewerColumn(column, viewerColumn); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canAlterColumnWidth(TreeColumn treeColumn) { + for (Column column : Column.values()) { + if (Objects.equals(getMappedTreeViewerColumn(column).getColumn(), treeColumn)) { + return (column.showInRawMode && rawMode) || (column.showInAggregatedMode && !rawMode); + } + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Set getPreferenceIds() { + Set preferences = EnumSet.noneOf(PreferenceId.class); + preferences.add(PreferenceId.INVOCATION_SUBVIEW_MODE); + return preferences; + } + + /** + * {@inheritDoc} + */ + @Override + public void preferenceEventFired(PreferenceEvent preferenceEvent) { + if (PreferenceId.INVOCATION_SUBVIEW_MODE.equals(preferenceEvent.getPreferenceId())) { + Map preferenceMap = preferenceEvent.getPreferenceMap(); + if (null != preferenceMap && preferenceMap.containsKey(PreferenceId.InvocationSubviewMode.RAW)) { + Boolean isRawMode = (Boolean) preferenceMap.get(PreferenceId.InvocationSubviewMode.RAW); + + // first show/hide columns and then change the rawMode value + handleRawAggregatedColumnVisibility(isRawMode.booleanValue()); + rawMode = isRawMode.booleanValue(); + } + } + } + + /** + * Handles the raw and aggregated columns hiding/showing. + * + * @param rawMode + * Is raw mode active. + */ + private void handleRawAggregatedColumnVisibility(boolean rawMode) { + for (Column column : Column.values()) { + if (rawMode) { + if (column.showInRawMode && !column.showInAggregatedMode && !ShowHideColumnsHandler.isColumnHidden(this.getClass(), column.name)) { + Integer width = ShowHideColumnsHandler.getRememberedColumnWidth(this.getClass(), column.name); + getMappedTreeViewerColumn(column).getColumn().setWidth((null != width) ? width.intValue() : column.width); + } else if (!column.showInRawMode && column.showInAggregatedMode) { + getMappedTreeViewerColumn(column).getColumn().setWidth(0); + } + } else { + if (!column.showInRawMode && column.showInAggregatedMode && !ShowHideColumnsHandler.isColumnHidden(this.getClass(), column.name)) { + Integer width = ShowHideColumnsHandler.getRememberedColumnWidth(this.getClass(), column.name); + getMappedTreeViewerColumn(column).getColumn().setWidth((null != width) ? width.intValue() : column.width); + } else if (column.showInRawMode && !column.showInAggregatedMode) { + getMappedTreeViewerColumn(column).getColumn().setWidth(0); + } + } + } + } + + /** + * {@inheritDoc} + */ + public IContentProvider getContentProvider() { + return new SqlInvocContentProvider(); + } + + /** + * {@inheritDoc} + */ + public ViewerComparator getComparator() { + TreeViewerComparator sqlInputViewerComparator = new DatabaseSqlTreeComparator(); + for (Column column : Column.values()) { + RawAggregatedResultComparator comparator = new RawAggregatedResultComparator(column.dataComparator, cachedDataService, column.showInRawMode, + column.showInAggregatedMode) { + @Override + protected boolean isRawMode() { + return rawMode; + } + }; + sqlInputViewerComparator.addColumn(getMappedTreeViewerColumn(column).getColumn(), comparator); + } + + return sqlInputViewerComparator; + } + + /** + * {@inheritDoc} + */ + public IBaseLabelProvider getLabelProvider() { + return new SqlInvocLabelProvider(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canOpenInput(List data) { + if (null == data) { + return false; + } + + if (data.isEmpty()) { + return true; + } + + // we accept one invocation sequence + if (data.get(0) instanceof InvocationSequenceData) { + return true; + } + + // or list of SQLs + if (data.get(0) instanceof SqlStatementData) { + return true; + } + + return false; + } + + /** + * {@inheritDoc} + */ + public void update(IProgressMonitor monitor) { + } + + /** + * The content provider for this view. + * + * @author Patrice Bouillet + * + */ + private final class SqlInvocContentProvider implements ITreeContentProvider { + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public Object[] getElements(Object inputElement) { + List input = (List) inputElement; + + if (CollectionUtils.isEmpty(input)) { + return new Object[0]; + } + + if (input.get(0) instanceof InvocationSequenceData) { + sqlStatementDataList = getRawInputList((List) input, new ArrayList()); + } else { + sqlStatementDataList = (List) input; + } + if (!rawMode) { + AggregationPerformer aggregationPerformer = new AggregationPerformer(new SqlStatementDataAggregator()); + aggregationPerformer.processCollection(sqlStatementDataList); + sqlStatementDataList = aggregationPerformer.getResultList(); + databaseSqlMap = createInputMap(sqlStatementDataList); + return databaseSqlMap.keySet().toArray(); + } else { + Collections.sort(sqlStatementDataList, new Comparator() { + @Override + public int compare(SqlStatementData o1, SqlStatementData o2) { + return o1.getTimeStamp().compareTo(o2.getTimeStamp()); + } + }); + return sqlStatementDataList.toArray(); + } + + } + + /** + * Create input map from list of {@link SqlStatementData}s. + * + * @param sqlStatementDatas + * {@link SqlStatementData}s + * @return Input map + */ + private Map> createInputMap(List sqlStatementDatas) { + Map> map = new HashMap<>(); + for (SqlStatementData sqlStatementData : sqlStatementDatas) { + DatabaseInfoHelper helper = new DatabaseInfoHelper(sqlStatementData); + List list = map.get(helper); + if (null == list) { + list = new ArrayList<>(); + map.put(helper, list); + } + list.add(sqlStatementData); + } + return map; + } + + /** + * Returns the raw list, with no aggregation. + * + * @param invocationSequenceDataList + * Input as list of invocations + * @param sqlStatementDataList + * List where results will be stored. Needed because of reflection. Note that + * this list will be returned as the result. + * @return List of raw order SQL data. + */ + private List getRawInputList(List invocationSequenceDataList, List sqlStatementDataList) { + for (InvocationSequenceData invocationSequenceData : invocationSequenceDataList) { + if (null != invocationSequenceData.getSqlStatementData()) { + sqlStatementDataList.add(invocationSequenceData.getSqlStatementData()); + } + if (null != invocationSequenceData.getNestedSequences() && !invocationSequenceData.getNestedSequences().isEmpty()) { + getRawInputList(invocationSequenceData.getNestedSequences(), sqlStatementDataList); + } + } + + return sqlStatementDataList; + } + + /** + * {@inheritDoc} + */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + + /** + * {@inheritDoc} + */ + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof DatabaseInfoHelper) { + List children = databaseSqlMap.get(parentElement); + if (CollectionUtils.isNotEmpty(children)) { + return children.toArray(); + } + } + return new Object[0]; + } + + /** + * {@inheritDoc} + */ + @Override + public Object getParent(Object element) { + if (!rawMode && element instanceof SqlStatementData) { + return new DatabaseInfoHelper((SqlStatementData) element); + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasChildren(Object element) { + return element instanceof DatabaseInfoHelper; + } + + } + + /** + * The sql label provider used by this view. + * + * @author Patrice Bouillet + * + */ + private final class SqlInvocLabelProvider extends StyledCellIndexLabelProvider { + + /** + * Creates the styled text. + * + * @param element + * The element to create the styled text for. + * @param index + * The index in the column. + * @return The created styled string. + */ + @Override + public StyledString getStyledText(Object element, int index) { + Column enumId = Column.fromOrd(index); + + if (element instanceof SqlStatementData) { + return getStyledTextForColumn((SqlStatementData) element, enumId); + } else if (element instanceof DatabaseInfoHelper) { + return getDatabaseStyledTextForColumn((DatabaseInfoHelper) element, enumId); + } else { + return emptyStyledString; + } + } + + /** + * {@inheritDoc} + */ + @Override + public String getToolTipText(Object element, int index) { + if (element instanceof DatabaseInfoHelper) { + DatabaseInfoHelper helper = (DatabaseInfoHelper) element; + return helper.getLongText(); + } + return super.getToolTipText(element, index); + } + + } + + /** + * Returns the styled text for a specific column. + * + * @param data + * The data object to extract the information from. + * @param enumId + * The enumeration ID. + * @return The styled string containing the information from the data object. + */ + private StyledString getStyledTextForColumn(SqlStatementData data, Column enumId) { + switch (enumId) { + case DATABASE_URL: + if (rawMode) { + if (StringUtils.isNotEmpty(data.getDatabaseUrl())) { + return new StyledString(data.getDatabaseUrl()); + } else { + return new StyledString("Unknown"); + } + } else { + return emptyStyledString; + } + case TIMESTAMP: + if (rawMode) { + return new StyledString(NumberFormatter.formatTimeWithMillis(data.getTimeStamp())); + } else { + return emptyStyledString; + } + case STATEMENT: + if (rawMode) { + String sql = TextFormatter.clearLineBreaks(data.getSqlWithParameterValues()); + return new StyledString(sql); + } else { + String sql = TextFormatter.clearLineBreaks(data.getSql()); + return new StyledString(sql); + } + case COUNT: + return new StyledString(Long.toString(data.getCount())); + case AVERAGE: + return new StyledString(NumberFormatter.formatDouble(data.getAverage())); + case MIN: + return new StyledString(NumberFormatter.formatDouble(data.getMin())); + case MAX: + return new StyledString(NumberFormatter.formatDouble(data.getMax())); + case DURATION: + return new StyledString(NumberFormatter.formatDouble(data.getDuration())); + case PREPARED: + if (rawMode) { + return new StyledString(Boolean.toString(data.isPreparedStatement())); + } else { + return emptyStyledString; + } + default: + return new StyledString("error"); + } + } + + /** + * Returns the styled text for a specific column. + * + * @param data + * {@link DatabaseInfoHelper}. + * @param enumId + * The enumeration ID. + * @return The styled string containing the information from the data object. + */ + private StyledString getDatabaseStyledTextForColumn(DatabaseInfoHelper data, Column enumId) { + switch (enumId) { + case DATABASE_URL: + return new StyledString(data.getDatabaseUrl()); + default: + return emptyStyledString; + } + } + + /** + * {@inheritDoc} + */ + @Override + public int getExpandLevel() { + return TreeViewer.ALL_LEVELS; + } + + /** + * {@inheritDoc} + */ + public String getReadableString(Object object) { + if (object instanceof SqlStatementData) { + SqlStatementData data = (SqlStatementData) object; + StringBuilder sb = new StringBuilder(); + for (Column column : Column.values()) { + sb.append(getStyledTextForColumn(data, column).toString()); + sb.append('\t'); + } + return sb.toString(); + } else if (object instanceof DatabaseInfoHelper) { + DatabaseInfoHelper data = (DatabaseInfoHelper) object; + StringBuilder sb = new StringBuilder(); + for (Column column : Column.values()) { + sb.append(getDatabaseStyledTextForColumn(data, column).toString()); + sb.append('\t'); + } + return sb.toString(); + } + throw new RuntimeException("Could not create the human readable string!"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getColumnValues(Object object) { + if (object instanceof SqlStatementData) { + SqlStatementData data = (SqlStatementData) object; + List values = new ArrayList(); + for (Column column : Column.values()) { + values.add(getStyledTextForColumn(data, column).toString()); + } + return values; + } else if (object instanceof DatabaseInfoHelper) { + DatabaseInfoHelper data = (DatabaseInfoHelper) object; + List values = new ArrayList(); + for (Column column : Column.values()) { + values.add(getDatabaseStyledTextForColumn(data, column).toString()); + } + return values; + } + throw new RuntimeException("Could not create the column values!"); + } + + /** + * {@inheritDoc} + */ + @Override + public Object[] getObjectsToSearch(Object treeInput) { + return sqlStatementDataList.toArray(); + } + + /** + * {@inheritDoc} + */ + @Override + public SubViewClassification getSubViewClassification() { + return SubViewClassification.SLAVE; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/SteppingInvocDetailInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/SteppingInvocDetailInputController.java new file mode 100644 index 000000000..897362e1b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/SteppingInvocDetailInputController.java @@ -0,0 +1,197 @@ +package info.novatec.inspectit.rcp.editor.tree.input; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.service.ICachedDataService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.util.ElementOccurrenceCount; +import info.novatec.inspectit.rcp.util.OccurrenceFinderFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.eclipse.jface.viewers.ViewerFilter; + +/** + * Extension of the {@link InvocDetailInputController} adapted to serve as an input for a + * {@link info.novatec.inspectit.rcp.editor.tree.SteppingTreeSubView}. + * + * @author Ivan Senic + * + */ +public class SteppingInvocDetailInputController extends InvocDetailInputController implements SteppingTreeInputController { + + /** + * The ID of this subview / controller. + */ + public static final String ID = "inspectit.subview.tree.steppinginvocdetail"; + + /** + * List of the objects that are possible to locate in the tree. + */ + private List steppingObjectsList; + + /** + * Global data access service. + */ + private ICachedDataService cachedDataService; + + /** + * Is stepping control be initially visible. + */ + private boolean initVisible; + + /** + * Constructor that defines if the stepping control is visible or not. + * + * @param initVisible + * Should stepping control be initially visible. + */ + public SteppingInvocDetailInputController(boolean initVisible) { + this.initVisible = initVisible; + } + + /** + * {@inheritDoc} + */ + @Override + public void setInputDefinition(InputDefinition inputDefinition) { + super.setInputDefinition(inputDefinition); + steppingObjectsList = new ArrayList(); + + if (inputDefinition.hasInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.NAVIGATION_STEPPING_EXTRAS_MARKER)) { + List steppingObj = inputDefinition.getInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.NAVIGATION_STEPPING_EXTRAS_MARKER).getSteppingTemplateList(); + if (null != steppingObj) { + for (DefaultData object : steppingObj) { + addObjectToSteppingObjectList(object); + } + } + } + + cachedDataService = inputDefinition.getRepositoryDefinition().getCachedDataService(); + } + + /** + * {@inheritDoc} + */ + @Override + public Set getPreferenceIds() { + Set preferences = super.getPreferenceIds(); + preferences.add(PreferenceId.STEPPABLE_CONTROL); + return preferences; + } + + /** + * {@inheritDoc} + */ + @Override + public List getSteppingObjectList() { + return steppingObjectsList; + } + + /** + * {@inheritDoc} + */ + @Override + public void addObjectToSteppingObjectList(Object template) { + if (!steppingObjectsList.contains(template)) { + steppingObjectsList.add(template); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean initSteppingControlVisible() { + return initVisible; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public ElementOccurrenceCount countOccurrences(Object element, ViewerFilter[] filters) { + List input = (List) getTreeInput(); + if (input != null && !input.isEmpty()) { + InvocationSequenceData invocation = (InvocationSequenceData) input.get(0); + return OccurrenceFinderFactory.getOccurrenceCount(invocation, element, filters); + } + return ElementOccurrenceCount.emptyElement(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isElementOccurrenceReachable(Object element, int occurance, ViewerFilter[] filters) { + Object object = getElement(element, occurance, filters); + if (null != object) { + return true; + } + return false; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public Object getElement(Object template, int occurance, ViewerFilter[] filters) { + List input = (List) getTreeInput(); + if (input != null && !input.isEmpty()) { + InvocationSequenceData invocation = (InvocationSequenceData) input.get(0); + return OccurrenceFinderFactory.getOccurrence(invocation, template, occurance, filters); + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public String getElementTextualRepresentation(Object invAwareData) { + if (invAwareData instanceof SqlStatementData) { + SqlStatementData sqlData = (SqlStatementData) invAwareData; + if (0 == sqlData.getId()) { + return "SQL: " + sqlData.getSql() + " [All]"; + } else { + return "SQL: " + sqlData.getSql() + " [Single]"; + } + } else if (invAwareData instanceof TimerData) { + TimerData timerData = (TimerData) invAwareData; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(timerData.getMethodIdent()); + if (0 == timerData.getId()) { + return TextFormatter.getMethodString(methodIdent) + " [All]"; + } else { + return TextFormatter.getMethodString(methodIdent) + " [Single]"; + } + } else if (invAwareData instanceof ExceptionSensorData) { + ExceptionSensorData exData = (ExceptionSensorData) invAwareData; + if (0 == exData.getId()) { + return "Exception: " + exData.getThrowableType() + " [All]"; + } else { + return "Exception: " + exData.getThrowableType() + " [Single]"; + } + } else if (invAwareData instanceof InvocationSequenceData) { + InvocationSequenceData invocationSequenceData = (InvocationSequenceData) invAwareData; + MethodIdent methodIdent = cachedDataService.getMethodIdentForId(invocationSequenceData.getMethodIdent()); + if (0 == invocationSequenceData.getId()) { + return TextFormatter.getMethodString(methodIdent) + " [All]"; + } else { + return TextFormatter.getMethodString(methodIdent) + " [Single]"; + } + } + return ""; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/SteppingTreeInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/SteppingTreeInputController.java new file mode 100644 index 000000000..34dbc5cfb --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/SteppingTreeInputController.java @@ -0,0 +1,88 @@ +package info.novatec.inspectit.rcp.editor.tree.input; + +import info.novatec.inspectit.rcp.util.ElementOccurrenceCount; + +import java.util.List; + +import org.eclipse.jface.viewers.ViewerFilter; + +/** + * An extension of {@link TreeInputController} that provides the necessary functionality for + * supporting {@link info.novatec.inspectit.rcp.editor.tree.SteppingTreeSubView}. + * + * @author Ivan Senic + * + */ +public interface SteppingTreeInputController extends TreeInputController { + + /** + * + * @return List of the objects that are possible to be located in the tree. + */ + List getSteppingObjectList(); + + /** + * Counts number of occurrences of one stepping element in the current tree input. + * + * @param element + * Template element to count occurrences for. + * @param filters + * Array of filters that each occurrence has to pass, so that it is included in the + * count. + * @return Number of occurrences. + */ + ElementOccurrenceCount countOccurrences(Object element, ViewerFilter[] filters); + + /** + * Checks if the supplied occurrence of one stepping element in reachable in the current tree + * input. + * + * @param element + * Template element. + * @param occurance + * Wanted occurrence. + * @param filters + * Array of filters that each occurrence has to pass, so that it is included in the + * count, and final result. + * @return True if wanted occurrence for the object is reachable, otherwise false. + */ + boolean isElementOccurrenceReachable(Object element, int occurance, ViewerFilter[] filters); + + /** + * Returns the concrete element from the tree input that correspond to the template element and + * wanted occurrence. This element can be further used to expand the tree viewer to it. + * + * @param template + * Template element. + * @param occurrence + * Wanted occurrence. + * @param filters + * Array of filters that each occurrence has to pass, so that it is included in the + * count. + * @return Concrete element or null if the wanted occurrence is not reachable. + */ + Object getElement(Object template, int occurrence, ViewerFilter[] filters); + + /** + * Returns the textual representation of the stepping element. + * + * @param element + * Element to get the representation for. + * @return Textual representation. + */ + String getElementTextualRepresentation(Object element); + + /** + * Registers a new object that should be provided for stepping functionality. + * + * @param element + * Object to be added to the list. + */ + void addObjectToSteppingObjectList(Object element); + + /** + * + * @return Returns if the sub-view should be loaded with stepping control visible or not. + */ + boolean initSteppingControlVisible(); +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/TreeInputController.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/TreeInputController.java new file mode 100644 index 000000000..e25f1b485 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/input/TreeInputController.java @@ -0,0 +1,189 @@ +package info.novatec.inspectit.rcp.editor.tree.input; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; +import info.novatec.inspectit.rcp.editor.root.SubViewClassificationController; + +import java.util.List; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.swt.widgets.TreeColumn; + +/** + * The interface for all tree input controller. + * + * @author Patrice Bouillet + * + */ +public interface TreeInputController extends SubViewClassificationController { + + /** + * Sets the input definition of this controller. + * + * @param inputDefinition + * The input definition. + */ + void setInputDefinition(InputDefinition inputDefinition); + + /** + * Creates the columns in the given tree viewer. + * + * @param treeViewer + * The tree viewer. + */ + void createColumns(TreeViewer treeViewer); + + /** + * The {@link info.novatec.inspectit.rcp.editor.tree.TreeSubView} might need to alter the column + * width/visibility if the column has the remembered size. With this method the controller gives + * or denies the {@link info.novatec.inspectit.rcp.editor.tree.TreeSubView} to alter the column + * width. + * + * @param treeColumn + * {@link TreeColumn} + * + * @return Returns true if the {@link TreeColumn} can be altered. + */ + boolean canAlterColumnWidth(TreeColumn treeColumn); + + /** + * This method will be called when a double click event is executed. + * + * @param event + * The event object. + */ + void doubleClick(DoubleClickEvent event); + + /** + * Generates and returns the input for the tree. Returning null is possible and + * indicates most of the time that there is no default list or object to display in the table. + * For some {@link DefaultData} objects, the method {@link #canOpenInput(List)} should return + * true so that the input object is set by the + * {@link info.novatec.inspectit.rcp.editor.tree.TreeSubView}. + * + * @return The tree input or null if nothing to display for default. + */ + Object getTreeInput(); + + /** + * Returns the content provider for the {@link TreeViewer}. + * + * @return The content provider. + * @see IContentProvider + */ + IContentProvider getContentProvider(); + + /** + * Returns the label provider for the {@link TreeViewer}. + * + * @return The label provider + * @see IBaseLabelProvider + */ + IBaseLabelProvider getLabelProvider(); + + /** + * Returns the comparator for the {@link TreeViewer}. Can be null to indicate that + * no sorting of the elements should be done. + * + * @return The tree viewer comparator. + */ + ViewerComparator getComparator(); + + /** + * Refreshes the current data and updates the tree input if new items are available. + * + * @param monitor + * The progress monitor. + * @param rootEditor + * RootEditor of the view that is being refreshed. + */ + void doRefresh(IProgressMonitor monitor, IRootEditor rootEditor); + + /** + * Returns true if the controller can open the input which consists of one or + * several {@link DefaultData} objects. + * + * @param data + * The data which is checked if the controller can open it. + * @return Returns true if the controller can open the input. + */ + boolean canOpenInput(List data); + + /** + * Returns all needed preference IDs. + * + * @return A {@link Set} containing all {@link PreferenceId}. Returning null is not + * permitted here. At least a {@link java.util.Collections#EMPTY_SET} should be + * returned. + */ + Set getPreferenceIds(); + + /** + * This method is called whenever something is changed in one of the preferences. + * + * @param preferenceEvent + * The event object containing the changed objects. + */ + void preferenceEventFired(PreferenceEvent preferenceEvent); + + /** + * This method creates a human readable string out of the given object (which is object from the + * tree model). + * + * @param object + * The object to create the string from. + * @return The created human readable string. + */ + String getReadableString(Object object); + + /** + * Return the values of all columns in the tree for the given object. Not visible columns values + * will also be included. The order of the values will be same to the initial tree column order, + * thus not reflecting the current state of the tree if the columns were moved. + * + * @param object + * Object to get values for. + * @return List of string representing the values. + */ + List getColumnValues(Object object); + + /** + * Returns an optional filter for this tree. + * + * @return the filter array. + */ + ViewerFilter[] getFilters(); + + /** + * Returns the level to which the viewer's tree should be expanded. + * + * @return The level to which the viewer's tree should be expanded. + */ + int getExpandLevel(); + + /** + * Returns the list of the objects that should be searched. + * + * @param treeInput + * Current input of the table. The {@link TreeInputController} is responsible to + * modify the input if necessary. + * @return Returns the list of the objects that should be searched. + */ + Object[] getObjectsToSearch(Object treeInput); + + /** + * Disposes the tree input. + */ + void dispose(); + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/util/DatabaseSqlTreeComparator.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/util/DatabaseSqlTreeComparator.java new file mode 100644 index 000000000..341c2f952 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/tree/util/DatabaseSqlTreeComparator.java @@ -0,0 +1,49 @@ +package info.novatec.inspectit.rcp.editor.tree.util; + +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.rcp.editor.tree.TreeViewerComparator; +import info.novatec.inspectit.rcp.util.data.DatabaseInfoHelper; + +import org.eclipse.jface.viewers.Viewer; + +import com.google.common.base.Objects; + +/** + * Special comparator to avoid the comparison of {@link DatabaseInfoHelper}s. + * + * @author Ivan Senic + * + */ +public class DatabaseSqlTreeComparator extends TreeViewerComparator { + + /** + * {@inheritDoc} + */ + @Override + public int compare(Viewer viewer, Object o1, Object o2) { + if (o1 instanceof SqlStatementData && o2 instanceof SqlStatementData) { + DatabaseInfoHelper d1 = new DatabaseInfoHelper((SqlStatementData) o1); + DatabaseInfoHelper d2 = new DatabaseInfoHelper((SqlStatementData) o2); + if (Objects.equal(d1, d2)) { + return super.compare(viewer, o1, o2); + } else { + return this.compareDatabaseInfoHelpers(d1, d2); + } + } else if (o1 instanceof DatabaseInfoHelper && o2 instanceof DatabaseInfoHelper) { + return this.compareDatabaseInfoHelpers((DatabaseInfoHelper) o1, (DatabaseInfoHelper) o2); + } + return 0; + } + + /** + * @param d1 + * {@link DatabaseInfoHelper} + * @param d2 + * {@link DatabaseInfoHelper} + * @return Returns result of the comparison of the database urls. + */ + private int compareDatabaseInfoHelpers(DatabaseInfoHelper d1, DatabaseInfoHelper d2) { + return d1.getDatabaseUrl().compareToIgnoreCase(d2.getDatabaseUrl()); + } + +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/viewers/AbstractViewerComparator.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/viewers/AbstractViewerComparator.java new file mode 100644 index 000000000..526e50646 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/viewers/AbstractViewerComparator.java @@ -0,0 +1,131 @@ +package info.novatec.inspectit.rcp.editor.viewers; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.comparator.ResultComparator; + +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.SWT; + +/** + * Viewer comparator uses provided comparators to sort specific columns. + * + * @author Patrice Boulliet + * @author Ivan Senic + * + * @param + * Type for which comparator is created. + */ +public abstract class AbstractViewerComparator extends ViewerComparator { + + /** + * The available sort states. + * + * @author Patrice Bouillet + * + */ + protected enum SortState { + /** State that won't sort. */ + NONE(SWT.NONE), + /** State that sorts upwards. */ + UP(SWT.UP), + /** State that sorts downwards. */ + DOWN(SWT.DOWN); + + /** + * The swt direction. + */ + private int swtDirection; + + /** + * Constructor to accept the swt direction. + * + * @param swtDirection + * The swt direction. + */ + private SortState(int swtDirection) { + this.swtDirection = swtDirection; + } + + /** + * Gets {@link #swtDirection}. + * + * @return {@link #swtDirection} + */ + public int getSwtDirection() { + return swtDirection; + } + + } + + /** + * Current comparator provider. + */ + private ResultComparator comparator; + + /** + * Default sort state. + */ + private SortState sortState = SortState.UP; + + /** + * Toggles the sorting of the column. + * + * @param id + * The comparator provider. + */ + protected void toggleSortColumn(ResultComparator id) { + if (comparator == id) { // NOPMD + switch (sortState) { + case NONE: + sortState = SortState.UP; + comparator.setAscending(true); + break; + case UP: + sortState = SortState.DOWN; + comparator.setAscending(false); + break; + case DOWN: + sortState = SortState.NONE; + break; + default: + break; + } + } else { + comparator = id; + sortState = SortState.UP; + comparator.setAscending(true); + } + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public int compare(Viewer viewer, Object o1, Object o2) { + if (null == comparator) { + return 0; + } + + // just return 0 if we don't want to sort + if (SortState.NONE.equals(sortState)) { + return 0; + } + + T e1 = (T) o1; + T e2 = (T) o2; + + return comparator.compare(e1, e2); + } + + /** + * Gets {@link #sortState}. + * + * @return {@link #sortState} + */ + protected SortState getSortState() { + return sortState; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/viewers/CheckedDelegatingIndexLabelProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/viewers/CheckedDelegatingIndexLabelProvider.java new file mode 100644 index 000000000..39688d217 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/viewers/CheckedDelegatingIndexLabelProvider.java @@ -0,0 +1,106 @@ +package info.novatec.inspectit.rcp.editor.viewers; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Image; + +/** + * Special {@link StyledCellIndexLabelProvider} that can be used when checked style in table + * sub-view is active. This provider delegates to original provide with the decreased index. + * + * @author Ivan Senic + * + */ +public class CheckedDelegatingIndexLabelProvider extends StyledCellIndexLabelProvider { + + /** + * Empty. + */ + private static final StyledString EMPTY_STYLED_STRING = new StyledString(""); + + /** + * Delegated {@link StyledCellIndexLabelProvider}. + */ + private StyledCellIndexLabelProvider delegate; + + /** + * @param delegate + * Delegated {@link StyledCellIndexLabelProvider}. + */ + public CheckedDelegatingIndexLabelProvider(StyledCellIndexLabelProvider delegate) { + Assert.isNotNull(delegate); + + this.delegate = delegate; + } + + /** + * {@inheritDoc} + */ + @Override + protected StyledString getStyledText(Object element, int index) { + if (0 == index) { + return EMPTY_STYLED_STRING; + } else { + return delegate.getStyledText(element, index - 1); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected Color getBackground(Object element, int index) { + if (0 == index) { + return null; + } else { + return delegate.getBackground(element, index - 1); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected Image getColumnImage(Object element, int index) { + if (0 == index) { + return null; + } else { + return delegate.getColumnImage(element, index - 1); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected Font getFont(Object element, int index) { + if (0 == index) { + return null; + } else { + return delegate.getFont(element, index - 1); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected Color getForeground(Object element, int index) { + if (0 == index) { + return null; + } else { + return delegate.getForeground(element, index - 1); + } + } + + /** + * {@inheritDoc} + */ + @Override + public String getToolTipText(Object element) { + return delegate.getToolTipText(element); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/viewers/RawAggregatedResultComparator.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/viewers/RawAggregatedResultComparator.java new file mode 100644 index 000000000..45d65b00d --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/viewers/RawAggregatedResultComparator.java @@ -0,0 +1,85 @@ +package info.novatec.inspectit.rcp.editor.viewers; + +import info.novatec.inspectit.cmr.service.ICachedDataService; +import info.novatec.inspectit.cmr.service.cache.CachedDataService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.comparator.IDataComparator; +import info.novatec.inspectit.communication.comparator.ResultComparator; + +/** + * Extension of {@link ResultComparator} to solve problems with raw/aggregated comparison on the UI. + * + * @author Ivan Senic + * + * @param + * Type that can be sorted with this comparator. + */ +public abstract class RawAggregatedResultComparator extends ResultComparator { + + /** + * If compare should be execute in raw mode. + */ + private boolean compareInRawMode; + + /** + * If compare should be executed in aggregated mode. + */ + private boolean compareInAggregatedMode; + + /** + * + * @param comparator + * Delegating comparator. + * @param cachedDataService + * {@link CachedDataService} + * @param compareInRawMode + * If compare should be execute in raw mode. + * @param compareInAggregatedMode + * If compare should be execute in aggregated mode. + */ + public RawAggregatedResultComparator(IDataComparator comparator, ICachedDataService cachedDataService, boolean compareInRawMode, boolean compareInAggregatedMode) { + this(comparator, cachedDataService, compareInRawMode, compareInAggregatedMode, true); + } + + /** + * + * @param comparator + * Delegating comparator. + * @param cachedDataService + * {@link CachedDataService} + * @param compareInRawMode + * If compare should be execute in raw mode. + * @param compareInAggregatedMode + * If compare should be execute in aggregated mode. + * @param ascending + * True if ascending sorting is on, false if descending sorting is on. + */ + public RawAggregatedResultComparator(IDataComparator comparator, ICachedDataService cachedDataService, boolean compareInRawMode, boolean compareInAggregatedMode, boolean ascending) { + super(comparator, cachedDataService, ascending); + this.compareInRawMode = compareInRawMode; + this.compareInAggregatedMode = compareInAggregatedMode; + } + + /** + * Returns if the raw mode for the table where comparing should be done is on. Sub-classes + * should provide implementations for this. + * + * @return Returns if the raw mode is on. + */ + protected abstract boolean isRawMode(); + + /** + * {@inheritDoc} + *

+ * Will only compare if the conditions are met. + */ + public int compare(T o1, T o2) { + if ((compareInRawMode && isRawMode()) || (compareInAggregatedMode && !isRawMode())) { + return super.compare(o1, o2); + } else { + return 0; + } + + }; + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/editor/viewers/StyledCellIndexLabelProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/editor/viewers/StyledCellIndexLabelProvider.java new file mode 100644 index 000000000..670ee7ef0 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/editor/viewers/StyledCellIndexLabelProvider.java @@ -0,0 +1,163 @@ +package info.novatec.inspectit.rcp.editor.viewers; + +import info.novatec.inspectit.rcp.editor.tooltip.IColumnToolTipProvider; + +import java.util.Arrays; + +import org.eclipse.jface.viewers.StyledCellLabelProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.progress.PendingUpdateAdapter; + +/** + * This class extends the {@link StyledCellLabelProvider} with support for the index of the cell, + * used in the {@link org.eclipse.swt.widgets.Tree} or the {@link org.eclipse.swt.widgets.Table}. + * + * @author Patrice Bouillet + * + */ +public class StyledCellIndexLabelProvider extends StyledCellLabelProvider implements IColumnToolTipProvider { + + /** + * {@inheritDoc} + */ + @Override + public void update(ViewerCell cell) { + Object element = cell.getElement(); + // we don't care about the PendingUpdateAdapter which is used in some of + // the trees and tables. + if (element instanceof PendingUpdateAdapter) { + cell.setText(element.toString()); + } else { + int index = cell.getColumnIndex(); + StyledString styledString = getStyledText(element, index); + String newText = styledString.toString(); + + StyleRange[] oldStyleRanges = cell.getStyleRanges(); + StyleRange[] newStyleRanges = null; + if (isOwnerDrawEnabled()) { + newStyleRanges = styledString.getStyleRanges(); + } + + if (!Arrays.equals(oldStyleRanges, newStyleRanges)) { + cell.setStyleRanges(newStyleRanges); + if (cell.getText().equals(newText)) { + // make sure there will be a refresh from a change + cell.setText(""); + } + } + + cell.setText(newText); + cell.setImage(getColumnImage(element, index)); + cell.setFont(getFont(element, index)); + cell.setForeground(getForeground(element, index)); + cell.setBackground(getBackground(element, index)); + } + } + + /** + * Default behavior is to return an empty instance of {@link StyledString}. Clients should + * override this method if needed. + * + * @param element + * The element for which to provide the styled label text + * @param index + * The index of the element. + * @return The styled text string used to label the element + */ + protected StyledString getStyledText(Object element, int index) { + return new StyledString(element.toString()); + } + + /** + * Default behavior is to return null. Clients should override this method if + * needed. + * + * @param element + * the element for which to provide the label image + * @param index + * the index of the element. + * @return the image used to label the element, or null if there is no image for + * the given object + */ + protected Image getColumnImage(Object element, int index) { + return null; + } + + /** + * Default behavior is to return null. Clients should override this method if + * needed. + * + * @param element + * the element + * @param index + * the index of the element. + * @return the font for the element, or null to use the default font + */ + protected Font getFont(Object element, int index) { + return null; + } + + /** + * Default behavior is to return null. Clients should override this method if + * needed. + * + * @param element + * the element + * @param index + * the index of the element. + * @return the foreground color for the element, or null to use the default + * foreground color + */ + protected Color getForeground(Object element, int index) { + return null; + } + + /** + * Default behavior is to return null. Clients should override this method if + * needed. + * + * @param element + * the element + * @param index + * the index of the element. + * @return the background color for the element, or null to use the default + * background color + */ + protected Color getBackground(Object element, int index) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public String getToolTipText(Object element, int index) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public Image getToolTipImage(Object element, int index) { + return null; + } + + /** + * {@inheritDoc} + *

+ * It is needed to return not null value when using the + * {@link IColumnToolTipProvider}, so that the tips from {@link IColumnToolTipProvider} can be + * displayed. + */ + @Override + public String getToolTipText(Object element) { + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/filter/FilterComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/filter/FilterComposite.java new file mode 100644 index 000000000..68f8a90ec --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/filter/FilterComposite.java @@ -0,0 +1,208 @@ +package info.novatec.inspectit.rcp.filter; + +import java.util.Objects; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.progress.UIJob; + +/** + * A composite that displays a text box with {@link SWT#ICON_SEARCH} and {@link SWT#ICON_CANCEL} + * properties. The text box has a default text that is displayed in gray color. This text can be + * defined in the constructor. + *

+ * The subclasses should implement two methods, one for executing the filter and one for canceling + * the filtering. + * + * @author Ivan Senic + * + */ +public abstract class FilterComposite extends Composite { + + /** + * Time in milliseconds that will be used to wait for another input character before filter is + * executed. + */ + private static final int FILTER_KEYRELEASED_DELAY = 300; + + /** + * Boolean that will keep if the filter was executed. This will help to determine if the + * subclasses should be informed via {@link #executeCancel()}. + */ + private boolean filterExecuted = false; + + /** + * Filter text box. + */ + private Text filterText; + + /** + * Default text. + */ + private String defaultText; + + /** + * {@link UIJob} that is executing the filter with delay. + */ + private UIJob filterJob; + + /** + * Default constructor. + * + * @param parent + * A widget which will be the parent of the new instance (cannot be null). + * @param style + * The style of widget to construct. + * @param defaultText + * Text to be displayed in the text box when no filtering is active. + * @see Composite#Composite(Composite, int) + */ + public FilterComposite(Composite parent, int style, String defaultText) { + super(parent, style); + this.defaultText = defaultText; + filterJob = new UIJob("Filter Storage") { + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + executeFilterInternal(); + return Status.OK_STATUS; + } + }; + filterJob.setUser(false); + init(); + } + + /** + * This method is called when filtering should be canceled. Subclasses should implement proper + * actions. + */ + protected abstract void executeCancel(); + + /** + * This method is called when filtering should occur. Subclasses should implement proper + * actions. + * + * @param filterString + * String that was entered as a criteria in the filter text box. + */ + protected abstract void executeFilter(String filterString); + + /** + * {@inheritDoc} + */ + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + filterText.setEnabled(enabled); + } + + /** + * Initializes the widget. + */ + private void init() { + setLayout(new GridLayout(1, false)); + + filterText = new Text(this, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL | SWT.ICON_SEARCH); + filterText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + setDefaultText(); + filterText.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + if (e.detail == SWT.CANCEL) { + if (!Objects.equals(filterText.getText(), defaultText)) { + executeCancelInternal(); + } + } + } + }); + filterText.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + if (Objects.equals(filterText.getText(), defaultText)) { + setEmptyText(); + } + } + + @Override + public void focusLost(FocusEvent e) { + if (Objects.equals(filterText.getText(), "")) { + setDefaultText(); + } + } + }); + filterText.addTraverseListener(new TraverseListener() { + @Override + public void keyTraversed(TraverseEvent e) { + if (e.detail == SWT.TRAVERSE_ESCAPE) { + filterJob.cancel(); + executeCancelInternal(); + } else if (e.detail == SWT.TRAVERSE_RETURN) { + filterJob.cancel(); + executeFilterInternal(); + } + } + }); + filterText.addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + if (Character.isLetterOrDigit(e.character) || e.keyCode == SWT.BS || e.keyCode == SWT.DEL) { + filterJob.cancel(); + filterJob.schedule(FILTER_KEYRELEASED_DELAY); + } + } + }); + + } + + /** + * Executes cancel internally. + */ + private void executeCancelInternal() { + if (filterExecuted) { + filterJob.cancel(); + executeCancel(); + filterExecuted = false; + } + setDefaultText(); + this.forceFocus(); + } + + /** + * Executes filter internally. + */ + private void executeFilterInternal() { + String filterString = filterText.getText().trim(); + executeFilter(filterString); + filterExecuted = true; + } + + /** + * Sets default text in gray color. + */ + private void setDefaultText() { + filterText.setText(defaultText); + filterText.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_GRAY)); + } + + /** + * Empties the text box and set font color to black. + */ + private void setEmptyText() { + filterText.setText(""); + filterText.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); + } +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/form/CmrRepositoryPropertyForm.java b/inspectIT/src/info/novatec/inspectit/rcp/form/CmrRepositoryPropertyForm.java new file mode 100644 index 000000000..c08595ac2 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/form/CmrRepositoryPropertyForm.java @@ -0,0 +1,684 @@ +package info.novatec.inspectit.rcp.form; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.cmr.CmrStatusData; +import info.novatec.inspectit.communication.data.cmr.RecordingData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.formatter.ImageFormatter; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.model.Component; +import info.novatec.inspectit.rcp.provider.ICmrRepositoryProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.recording.RecordingState; +import info.novatec.inspectit.util.ObjectUtils; + +import java.text.DateFormat; +import java.util.Date; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.dialogs.PopupDialog; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.ProgressBar; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.forms.ManagedForm; +import org.eclipse.ui.forms.events.HyperlinkAdapter; +import org.eclipse.ui.forms.events.HyperlinkEvent; +import org.eclipse.ui.forms.widgets.FormText; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.ScrolledForm; +import org.eclipse.ui.forms.widgets.Section; +import org.eclipse.ui.forms.widgets.TableWrapData; +import org.eclipse.ui.forms.widgets.TableWrapLayout; +import org.eclipse.ui.progress.UIJob; + +/** + * Class having a form for displaying the properties of a {@link CmrRepositoryDefinition}. + * + * @author Ivan Senic + * + */ +public class CmrRepositoryPropertyForm implements ISelectionChangedListener { + + /** + * Number of max characters displayed for CMR description. + */ + private static final int MAX_DESCRIPTION_LENGTH = 150; + + /** + * {@link CmrRepositoryDefinition} to be displayed. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * Current recording data. + */ + private RecordingData recordingData; + + /** + * Job for recording end count-down. + */ + private RecordCountdownJob recordCountdownJob = new RecordCountdownJob(); + + /** + * Job for updating the CMR properties. + */ + private UpdateCmrPropertiesJob updateCmrPropertiesJob = new UpdateCmrPropertiesJob(); + + private Composite mainComposite; // NOCHK + private FormToolkit toolkit; // NOCHK + private ManagedForm managedForm; // NOCHK + private ScrolledForm form; // NOCHK + private Label address; // NOCHK + private FormText description; // NOCHK + private Label recordingIcon; // NOCHK + private Label recordingStorage; // NOCHK + private Label status; // NOCHK + private Label bufferDate; // NOCHK + private ProgressBar bufferBar; // NOCHK + private ProgressBar recTimeBar; // NOCHK + private Label recordingStatusIcon; // NOCHK + private Label recordingLabel; // NOCHK + private Label version; // NOCHK + private Label bufferSize; // NOCHK + private Label recTime; // NOCHK + private Label spaceLeftLabel; // NOCHK + private ProgressBar spaceLeftBar; // NOCHK + private Label uptimeLabel; // NOCHK + private Label databaseSizeLabel; // NOCHK + + /** + * Default constructor. + * + * @param parent + * Parent composite. + */ + public CmrRepositoryPropertyForm(Composite parent) { + this(parent, null); + } + + /** + * Secondary constructor. Set the displayed {@link CmrRepositoryDefinition}. + * + * @param parent + * Parent composite. + * @param cmrRepositoryDefinition + * Displayed CMR. + */ + public CmrRepositoryPropertyForm(Composite parent, CmrRepositoryDefinition cmrRepositoryDefinition) { + this.managedForm = new ManagedForm(parent); + this.toolkit = managedForm.getToolkit(); + this.form = managedForm.getForm(); + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + initWidget(); + } + + /** + * Instantiate the widgets. + */ + private void initWidget() { + Composite body = form.getBody(); + body.setLayout(new TableWrapLayout()); + managedForm.getToolkit().decorateFormHeading(form.getForm()); + mainComposite = toolkit.createComposite(body, SWT.NONE); + mainComposite.setLayout(new TableWrapLayout()); + mainComposite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + + // START - General section + Section generalSection = toolkit.createSection(mainComposite, Section.TITLE_BAR); + generalSection.setText("General information"); + + Composite generalComposite = toolkit.createComposite(generalSection, SWT.NONE); + TableWrapLayout tableWrapLayout = new TableWrapLayout(); + tableWrapLayout.numColumns = 2; + generalComposite.setLayout(tableWrapLayout); + generalComposite.setLayoutData(new TableWrapData(TableWrapData.FILL)); + + toolkit.createLabel(generalComposite, "Address:"); + address = toolkit.createLabel(generalComposite, null, SWT.WRAP); + + toolkit.createLabel(generalComposite, "Version:"); + version = toolkit.createLabel(generalComposite, null, SWT.WRAP); + + toolkit.createLabel(generalComposite, "Description:"); + description = toolkit.createFormText(generalComposite, true); + description.setLayoutData(new TableWrapData(TableWrapData.FILL)); + description.addHyperlinkListener(new HyperlinkAdapter() { + @Override + public void linkActivated(HyperlinkEvent e) { + showCmrDescriptionBox(); + } + }); + + toolkit.createLabel(generalComposite, "Status:"); + status = toolkit.createLabel(generalComposite, null, SWT.WRAP); + + toolkit.createLabel(generalComposite, "Uptime:"); + uptimeLabel = toolkit.createLabel(generalComposite, null, SWT.WRAP); + uptimeLabel.setToolTipText("Date started represents date/time on machine where CMR has been launched"); + + toolkit.createLabel(generalComposite, "Database size:"); + databaseSizeLabel = toolkit.createLabel(generalComposite, null, SWT.WRAP); + databaseSizeLabel.setToolTipText("Current size of the database on the CMR"); + + generalSection.setClient(generalComposite); + generalSection.setLayout(new TableWrapLayout()); + generalSection.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + // END - General section + + // START - Buffer section + Section bufferSection = toolkit.createSection(mainComposite, Section.TITLE_BAR); + bufferSection.setText("Buffer status"); + + Composite bufferSectionComposite = toolkit.createComposite(bufferSection, SWT.NONE); + tableWrapLayout = new TableWrapLayout(); + tableWrapLayout.numColumns = 2; + bufferSectionComposite.setLayout(tableWrapLayout); + bufferSectionComposite.setLayoutData(new TableWrapData(TableWrapData.FILL)); + + toolkit.createLabel(bufferSectionComposite, "Data in buffer since:"); + bufferDate = toolkit.createLabel(bufferSectionComposite, null, SWT.WRAP); + + toolkit.createLabel(bufferSectionComposite, "Buffer occupancy:"); + bufferBar = new ProgressBar(bufferSectionComposite, SWT.SMOOTH); + bufferBar.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + + // help label + toolkit.createLabel(bufferSectionComposite, null); + + bufferSize = toolkit.createLabel(bufferSectionComposite, null, SWT.CENTER); + bufferSize.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + + bufferSection.setClient(bufferSectionComposite); + bufferSection.setLayout(new TableWrapLayout()); + bufferSection.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + // END - Buffer section + + // START - Storage section + Section storageSection = toolkit.createSection(mainComposite, Section.TITLE_BAR); + storageSection.setText("Storage status"); + + Composite storageSectionComposite = toolkit.createComposite(storageSection, SWT.NONE); + tableWrapLayout = new TableWrapLayout(); + tableWrapLayout.numColumns = 2; + storageSectionComposite.setLayout(tableWrapLayout); + storageSectionComposite.setLayoutData(new TableWrapData(TableWrapData.FILL)); + + toolkit.createLabel(storageSectionComposite, "Storage space left:"); + + spaceLeftBar = new ProgressBar(storageSectionComposite, SWT.SMOOTH); + spaceLeftBar.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + + // help label + toolkit.createLabel(storageSectionComposite, null); + + spaceLeftLabel = toolkit.createLabel(storageSectionComposite, null, SWT.CENTER); + spaceLeftLabel.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + + toolkit.createLabel(storageSectionComposite, "Recording:"); + Composite recordingHelpComposite = toolkit.createComposite(storageSectionComposite); + GridLayout gl = new GridLayout(2, false); + gl.marginHeight = 0; + gl.marginWidth = 0; + recordingHelpComposite.setLayout(gl); + recordingIcon = toolkit.createLabel(recordingHelpComposite, null, SWT.WRAP); + recordingLabel = toolkit.createLabel(recordingHelpComposite, null, SWT.WRAP); + + toolkit.createLabel(storageSectionComposite, "Recording status:"); + recordingStatusIcon = toolkit.createLabel(storageSectionComposite, null, SWT.NONE); + + toolkit.createLabel(storageSectionComposite, "Recording storage:"); + recordingStorage = toolkit.createLabel(storageSectionComposite, null, SWT.WRAP); + + toolkit.createLabel(storageSectionComposite, "Recording time left:"); + + recTimeBar = new ProgressBar(storageSectionComposite, SWT.SMOOTH); + recTimeBar.setVisible(false); + recTimeBar.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + + // help label + toolkit.createLabel(storageSectionComposite, null); + + // + recTime = toolkit.createLabel(storageSectionComposite, null, SWT.CENTER); + recTime.setVisible(false); + recTime.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + + storageSection.setClient(storageSectionComposite); + tableWrapLayout = new TableWrapLayout(); + tableWrapLayout.numColumns = 2; + storageSection.setLayout(tableWrapLayout); + storageSection.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + // END - Storage section + + refreshData(); + } + + /** + * Sets layout data for the form. + * + * @param layoutData + * LayoutData. + */ + public void setLayoutData(Object layoutData) { + form.setLayoutData(layoutData); + } + + /** + * Refreshes the property form. + */ + public void refresh() { + refreshData(); + } + + /** + * {@inheritDoc} + */ + @Override + public void selectionChanged(SelectionChangedEvent event) { + ISelection selection = event.getSelection(); + if (!selection.isEmpty() && selection instanceof StructuredSelection) { + StructuredSelection structuredSelection = (StructuredSelection) selection; + Object firstElement = structuredSelection.getFirstElement(); + if (!(firstElement instanceof Component)) { + // it is possible that the PendingAdapterUpdate is in the selection because it + // is still loading the agents + return; + } + + while (firstElement != null) { + if (firstElement instanceof ICmrRepositoryProvider) { // NOPMD + if (!ObjectUtils.equals(cmrRepositoryDefinition, ((ICmrRepositoryProvider) firstElement).getCmrRepositoryDefinition())) { + cmrRepositoryDefinition = ((ICmrRepositoryProvider) firstElement).getCmrRepositoryDefinition(); + refreshData(); + } + return; + } + firstElement = ((Component) firstElement).getParent(); + } + } + if (null != cmrRepositoryDefinition) { + cmrRepositoryDefinition = null; // NOPMD + refreshData(); + } + + } + + /** + * Shows the description box. + */ + private void showCmrDescriptionBox() { + int shellStyle = SWT.CLOSE | SWT.TITLE | SWT.BORDER | SWT.APPLICATION_MODAL | SWT.RESIZE; + PopupDialog popupDialog = new PopupDialog(form.getShell(), shellStyle, true, false, false, false, false, "CMR description", "CMR description") { + private static final int CURSOR_SIZE = 15; + + @Override + protected Control createDialogArea(Composite parent) { + Composite composite = (Composite) super.createDialogArea(parent); + Text text = toolkit.createText(parent, null, SWT.MULTI | SWT.READ_ONLY | SWT.WRAP | SWT.H_SCROLL | SWT.V_SCROLL); + GridData gd = new GridData(GridData.BEGINNING | GridData.FILL_BOTH); + gd.horizontalIndent = 3; + gd.verticalIndent = 3; + text.setLayoutData(gd); + text.setText(cmrRepositoryDefinition.getDescription()); + return composite; + } + + @Override + protected Point getInitialLocation(Point initialSize) { + // show popup relative to cursor + Display display = getShell().getDisplay(); + Point location = display.getCursorLocation(); + location.x += CURSOR_SIZE; + location.y += CURSOR_SIZE; + return location; + } + + @Override + protected Point getInitialSize() { + return new Point(400, 200); + } + }; + popupDialog.open(); + + } + + /** + * Refreshes the data on the view. + */ + private void refreshData() { + // we only schedule if the cancel returns true + // because cancel fails when job is currently in process + if (updateCmrPropertiesJob.cancel()) { + updateCmrPropertiesJob.schedule(); + } + } + + /** + * Updates buffer data. + * + * @param cmrStatusData + * Status data. + */ + private void updateCmrManagementData(CmrStatusData cmrStatusData) { + boolean dataLoaded = false; + if (null != cmrStatusData) { + dataLoaded = true; + // Transfer to MB right away + double bufferMaxOccupancy = (double) cmrStatusData.getMaxBufferSize() / (1024 * 1024); + double bufferCurrentOccupancy = (double) cmrStatusData.getCurrentBufferSize() / (1024 * 1024); + bufferBar.setMaximum((int) Math.round(bufferMaxOccupancy)); + bufferBar.setSelection((int) Math.round(bufferCurrentOccupancy)); + int occupancy = (int) (100 * Math.round(bufferCurrentOccupancy) / Math.round(bufferMaxOccupancy)); + + String occMb = NumberFormatter.humanReadableByteCount(cmrStatusData.getCurrentBufferSize()); + String maxMb = NumberFormatter.humanReadableByteCount(cmrStatusData.getMaxBufferSize()); + String string = occupancy + "% (" + occMb + " / " + maxMb + ")"; + bufferSize.setText(string); + + DefaultData oldestData = cmrStatusData.getBufferOldestElement(); + if (null != oldestData) { + bufferDate.setText(NumberFormatter.formatTime(oldestData.getTimeStamp().getTime())); + } else { + bufferDate.setText("-"); + } + + // hard drive space data + int spaceOccupancy = (int) (100 * (double) cmrStatusData.getStorageDataSpaceLeft() / cmrStatusData.getStorageMaxDataSpace()); + StringBuilder spaceLeftStringBuilder = new StringBuilder(String.valueOf(spaceOccupancy)); + spaceLeftStringBuilder.append("% ("); + spaceLeftStringBuilder.append(NumberFormatter.humanReadableByteCount(cmrStatusData.getStorageDataSpaceLeft())); + spaceLeftStringBuilder.append(" / "); + spaceLeftStringBuilder.append(NumberFormatter.humanReadableByteCount(cmrStatusData.getStorageMaxDataSpace())); + spaceLeftStringBuilder.append(')'); + spaceLeftLabel.setText(spaceLeftStringBuilder.toString()); + spaceLeftBar.setMaximum((int) (cmrStatusData.getStorageMaxDataSpace() / 1024 / 1024)); + spaceLeftBar.setSelection((int) (cmrStatusData.getStorageDataSpaceLeft() / 1024 / 1024)); + if (!cmrStatusData.isCanWriteMore()) { + spaceLeftBar.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED)); + spaceLeftBar.setToolTipText("Space left is critically low and no write is possible anymore"); + } else if (cmrStatusData.isWarnSpaceLeftActive()) { + spaceLeftBar.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_YELLOW)); + spaceLeftBar.setToolTipText("Space left is reaching critical level"); + } else { + spaceLeftBar.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_GREEN)); + spaceLeftBar.setToolTipText("Enough space left"); + } + + // uptime info + long uptimeMillis = cmrStatusData.getUpTime(); + Date started = cmrStatusData.getDateStarted(); + StringBuilder uptimeText = new StringBuilder(NumberFormatter.humanReadableMillisCount(uptimeMillis, true)); + uptimeText.append(" (started "); + uptimeText.append(DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(started)); + uptimeText.append(')'); + uptimeLabel.setText(uptimeText.toString()); + + // database info + Long databaseSize = cmrStatusData.getDatabaseSize(); + if (null != databaseSize) { + databaseSizeLabel.setText(NumberFormatter.humanReadableByteCount(databaseSize.longValue())); + } else { + databaseSizeLabel.setText("n/a"); + } + } + + if (!dataLoaded) { + bufferDate.setText(""); + bufferBar.setMaximum(Integer.MAX_VALUE); + bufferBar.setSelection(0); + bufferSize.setText(""); + spaceLeftBar.setMaximum(Integer.MAX_VALUE); + spaceLeftBar.setSelection(0); + spaceLeftBar.setToolTipText(""); + spaceLeftLabel.setText(""); + uptimeLabel.setText(""); + databaseSizeLabel.setText(""); + } + } + + /** + * Updates recording data. + * + * @param recordingData + * Recording information. + */ + private void updateRecordingData(RecordingData recordingData) { + boolean countdownJobActive = false; + boolean dataLoaded = false; + recordingIcon.setImage(null); + // recording information + if (null != recordingData) { + RecordingState recordingState = cmrRepositoryDefinition.getStorageService().getRecordingState(); + if (recordingState == RecordingState.ON) { + recordingIcon.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_RECORD)); + recordingLabel.setText("Active"); + } else if (recordingState == RecordingState.SCHEDULED) { + recordingIcon.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_RECORD_SCHEDULED)); + recordingLabel.setText("Scheduled @ " + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(recordingData.getRecordStartDate())); + } + // get the storage name + StorageData storage = recordingData.getRecordingStorage(); + if (null != storage) { + recordingStorage.setText(storage.getName()); + } else { + recordingStorage.setText(""); + } + + // check if the recording time is limited + if (null != recordingData.getRecordEndDate()) { + countdownJobActive = true; + } else { + recTimeBar.setVisible(false); + recTime.setVisible(false); + } + + // recording status stuff + recordingStatusIcon.setImage(ImageFormatter.getWritingStatusImage(recordingData.getRecordingWritingStatus())); + recordingStatusIcon.setToolTipText(TextFormatter.getWritingStatusText(recordingData.getRecordingWritingStatus())); + + dataLoaded = true; + } else { + recordingIcon.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_RECORD_GRAY)); + } + + if (!dataLoaded) { + recordingStorage.setText(""); + recTimeBar.setVisible(false); + recTime.setVisible(false); + recordingStatusIcon.setImage(null); + recordingStatusIcon.setToolTipText(""); + recordingLabel.setText("Not Active"); + } + + if (countdownJobActive) { + recordCountdownJob.schedule(); + } else { + recordCountdownJob.cancel(); + } + } + + /** + * + * @return Returns if the form is disposed. + */ + public boolean isDisposed() { + return form.isDisposed(); + } + + /** + * Disposes the for. + */ + public void dispose() { + form.dispose(); + recordCountdownJob.cancel(); + } + + /** + * Job for updating the information about the CMR. Job will perform all UI related work in UI + * thread asynchronously. + * + * @author Ivan Senic + * + */ + private class UpdateCmrPropertiesJob extends Job { + + /** + * Default constructor. + */ + public UpdateCmrPropertiesJob() { + super("Updating CMR Properties.."); + } + + /** + * {@inheritDoc} + */ + @Override + protected IStatus run(IProgressMonitor monitor) { + final CmrRepositoryDefinition cmrRepositoryDefinition = CmrRepositoryPropertyForm.this.cmrRepositoryDefinition; + if (cmrRepositoryDefinition != null) { + final OnlineStatus onlineStatus = cmrRepositoryDefinition.getOnlineStatus(); + final CmrStatusData cmrStatusData = (onlineStatus == OnlineStatus.ONLINE) ? cmrRepositoryDefinition.getCmrManagementService().getCmrStatusData() : null; // NOPMD + recordingData = (onlineStatus == OnlineStatus.ONLINE) ? cmrRepositoryDefinition.getStorageService().getRecordingData() : null; // NOPMD + Display.getDefault().asyncExec(new Runnable() { + + @Override + public void run() { + form.setBusy(true); + form.setText(cmrRepositoryDefinition.getName()); + form.setMessage(null, IMessageProvider.NONE); + address.setText(cmrRepositoryDefinition.getIp() + ":" + cmrRepositoryDefinition.getPort()); + version.setText(cmrRepositoryDefinition.getVersion()); + String desc = cmrRepositoryDefinition.getDescription(); + if (null != desc) { + if (desc.length() > MAX_DESCRIPTION_LENGTH) { + description.setText("

" + desc.substring(0, MAX_DESCRIPTION_LENGTH) + ".. [More]

", true, false); + } else { + description.setText(desc, false, false); + } + } else { + description.setText("", false, false); + } + status.setText(onlineStatus.toString()); + if (onlineStatus == OnlineStatus.ONLINE) { + form.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SERVER_ONLINE_SMALL)); + } else if (onlineStatus == OnlineStatus.CHECKING) { + form.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SERVER_REFRESH_SMALL)); + } else { + form.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SERVER_OFFLINE_SMALL)); + } + + updateRecordingData(recordingData); + updateCmrManagementData(cmrStatusData); + + mainComposite.setVisible(true); + form.getBody().layout(true, true); + form.setBusy(false); + } + }); + } else { + + Display.getDefault().asyncExec(new Runnable() { + + @Override + public void run() { + form.setBusy(true); + + form.setText(null); + form.setMessage("Please select a CMR to see its properties.", IMessageProvider.INFORMATION); + mainComposite.setVisible(false); + + updateRecordingData(null); + updateCmrManagementData(null); + + mainComposite.setVisible(true); + form.getBody().layout(true, true); + form.setBusy(false); + } + }); + } + return Status.OK_STATUS; + } + } + + /** + * Class for updating the recording count down. + * + * @author Ivan Senic + * + */ + private class RecordCountdownJob extends UIJob { + + /** + * Default constructor. + */ + public RecordCountdownJob() { + super("Update Recording Countdown"); + setUser(false); + } + + /** + * {@inheritDoc} + */ + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + if (null != recordingData && !form.isDisposed()) { + Date endDate = recordingData.getRecordEndDate(); + Date startDate = recordingData.getRecordStartDate(); + if (null != endDate && null != startDate && startDate.before(new Date())) { + Date now = new Date(); + long millisMore = endDate.getTime() - now.getTime(); + if (millisMore > 0) { + if (!recTimeBar.isVisible()) { + recTimeBar.setVisible(true); + } + recTimeBar.setMaximum((int) (endDate.getTime() - startDate.getTime())); + recTimeBar.setSelection((int) (recTimeBar.getMaximum() - (now.getTime() - startDate.getTime()))); + + if (!recTime.isVisible()) { + recTime.setVisible(true); + } + String string; + if (millisMore > 0) { + string = NumberFormatter.humanReadableMillisCount(millisMore, false); + } else { + string = ""; + } + recTime.setText(string); + + } else { + if (recTimeBar.isVisible()) { + recTimeBar.setVisible(false); + } + if (recTime.isVisible()) { + recTime.setVisible(false); + } + this.cancel(); + refreshData(); + } + } + } + this.schedule(1000); + return Status.OK_STATUS; + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/form/StorageDataPropertyForm.java b/inspectIT/src/info/novatec/inspectit/rcp/form/StorageDataPropertyForm.java new file mode 100644 index 000000000..3fdbac986 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/form/StorageDataPropertyForm.java @@ -0,0 +1,604 @@ +package info.novatec.inspectit.rcp.form; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.ImageFormatter; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.handlers.AddStorageLabelHandler; +import info.novatec.inspectit.rcp.handlers.RemoveStorageLabelHandler; +import info.novatec.inspectit.rcp.provider.ILocalStorageDataProvider; +import info.novatec.inspectit.rcp.provider.IStorageDataProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.storage.label.edit.LabelValueEditingSupport; +import info.novatec.inspectit.rcp.storage.label.edit.LabelValueEditingSupport.LabelEditListener; +import info.novatec.inspectit.rcp.view.impl.StorageManagerView; +import info.novatec.inspectit.storage.IStorageData; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.dialogs.PopupDialog; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.forms.ManagedForm; +import org.eclipse.ui.forms.events.HyperlinkAdapter; +import org.eclipse.ui.forms.events.HyperlinkEvent; +import org.eclipse.ui.forms.widgets.FormText; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.ScrolledForm; +import org.eclipse.ui.forms.widgets.Section; +import org.eclipse.ui.forms.widgets.TableWrapData; +import org.eclipse.ui.forms.widgets.TableWrapLayout; +import org.eclipse.ui.handlers.IHandlerService; + +/** + * Form for displaying the {@link StorageData} properties. + * + * @author Ivan Senic + * + */ +public class StorageDataPropertyForm implements ISelectionChangedListener { + + /** + * Number of max characters displayed for storage description. + */ + private static final int MAX_DESCRIPTION_LENGTH = 150; + + /** + * Storage data holding the main information about the storage. + */ + private IStorageData storageData; + + /** + * Leaf that is displayed currently. + */ + private IStorageDataProvider storageDataProvider; + + /** + * Toolkit used to create widgets. + */ + private FormToolkit toolkit; + + /** + * {@link ManagedForm}. + */ + private ManagedForm managedForm; + + /** + * Form that will be created. + */ + private ScrolledForm form; + + /** + * Label for ID. + */ + private Label uniqueId; + + /** + * Label for repository. + */ + private Label repository; + + /** + * Label for description. + */ + private FormText description; + + /** + * Label for size on disk. + */ + private Label sizeOnDisk; + + /** + * Label for storage state. + */ + private Label state; + + /** + * Table of storage labels. + */ + private TableViewer labelsTableViewer; + + /** + * Add new label button. + */ + private Button addNewLabel; + + /** + * remove labels button. + */ + private Button removeLabels; + + /** + * Main composite where widgets are. + */ + private Composite mainComposite; + + /** + * {@link TableViewerColumn} for label values. Needed for editing support. + */ + private TableViewerColumn valueViewerColumn; + + /** + * Default constructor. + * + * @param parent + * Parent where form will be created. + */ + public StorageDataPropertyForm(Composite parent) { + this(parent, null); + } + + /** + * Secondary constructor. Set the displayed storage leaf. + * + * @param parent + * Parent where form will be created. + * @param storageDataProvider + * {@link IStorageDataProvider} to display. + */ + public StorageDataPropertyForm(Composite parent, IStorageDataProvider storageDataProvider) { + this.managedForm = new ManagedForm(parent); + this.toolkit = managedForm.getToolkit(); + this.form = managedForm.getForm(); + this.storageDataProvider = storageDataProvider; + if (null != storageDataProvider) { + this.storageData = storageDataProvider.getStorageData(); + } + initWidget(); + } + + /** + * Third constructor. Lets set everything. + * + * @param parent + * Parent where form will be created. + * @param storageDataProvider + * {@link IStorageDataProvider} to display. Can be null. + * @param storageData + * {@link IStorageData} to display. Can be null. + */ + public StorageDataPropertyForm(Composite parent, IStorageDataProvider storageDataProvider, IStorageData storageData) { + this.managedForm = new ManagedForm(parent); + this.toolkit = managedForm.getToolkit(); + this.form = managedForm.getForm(); + this.storageDataProvider = storageDataProvider; + this.storageData = storageData; + initWidget(); + } + + /** + * Instantiate the widgets. + */ + private void initWidget() { + Composite body = form.getBody(); + body.setLayout(new TableWrapLayout()); + managedForm.getToolkit().decorateFormHeading(form.getForm()); + mainComposite = toolkit.createComposite(body, SWT.NONE); + mainComposite.setLayout(new TableWrapLayout()); + mainComposite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + + // START - General section + Section generalSection = toolkit.createSection(mainComposite, Section.TITLE_BAR); + generalSection.setText("General information"); + + Composite generalComposite = toolkit.createComposite(generalSection, SWT.NONE); + TableWrapLayout tableWrapLayout = new TableWrapLayout(); + tableWrapLayout.numColumns = 2; + generalComposite.setLayout(tableWrapLayout); + generalComposite.setLayoutData(new TableWrapData(TableWrapData.FILL)); + + toolkit.createLabel(generalComposite, "Repository:"); + repository = toolkit.createLabel(generalComposite, null, SWT.WRAP); + + toolkit.createLabel(generalComposite, "Description:"); + description = toolkit.createFormText(generalComposite, true); + description.setLayoutData(new TableWrapData(TableWrapData.FILL)); + description.addHyperlinkListener(new HyperlinkAdapter() { + @Override + public void linkActivated(HyperlinkEvent e) { + showStorageDescriptionBox(); + } + }); + + toolkit.createLabel(generalComposite, "Size on disk:"); + sizeOnDisk = toolkit.createLabel(generalComposite, null, SWT.WRAP); + + toolkit.createLabel(generalComposite, "State:"); + state = toolkit.createLabel(generalComposite, null, SWT.WRAP); + + toolkit.createLabel(generalComposite, "Unique ID:"); + uniqueId = toolkit.createLabel(generalComposite, null, SWT.WRAP); + uniqueId.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + + generalSection.setClient(generalComposite); + generalSection.setLayout(new TableWrapLayout()); + generalSection.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + // END - General section + + // START - Label section + Section labelSection = toolkit.createSection(mainComposite, Section.TITLE_BAR); + labelSection.setText("Labels"); + + Composite labelComposite = toolkit.createComposite(labelSection, SWT.NONE); + tableWrapLayout = new TableWrapLayout(); + tableWrapLayout.numColumns = 2; + labelComposite.setLayout(tableWrapLayout); + labelComposite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + + Table table = toolkit.createTable(labelComposite, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.VIRTUAL); + table.setHeaderVisible(true); + table.setLinesVisible(true); + TableWrapData tableWrapData = new TableWrapData(TableWrapData.FILL_GRAB); + tableWrapData.colspan = 2; + tableWrapData.heightHint = 150; + table.setLayoutData(tableWrapData); + + labelsTableViewer = new TableViewer(table); + + TableViewerColumn viewerColumn = new TableViewerColumn(labelsTableViewer, SWT.NONE); + viewerColumn.getColumn().setText("Type"); + viewerColumn.getColumn().setMoveable(false); + viewerColumn.getColumn().setResizable(true); + viewerColumn.getColumn().setWidth(140); + + valueViewerColumn = new TableViewerColumn(labelsTableViewer, SWT.NONE); + valueViewerColumn.getColumn().setText("Value"); + valueViewerColumn.getColumn().setMoveable(false); + valueViewerColumn.getColumn().setResizable(true); + valueViewerColumn.getColumn().setWidth(140); + + labelsTableViewer.setContentProvider(new ArrayContentProvider()); + labelsTableViewer.setLabelProvider(new StyledCellIndexLabelProvider() { + @Override + protected StyledString getStyledText(Object element, int index) { + if (element instanceof AbstractStorageLabel) { + AbstractStorageLabel label = (AbstractStorageLabel) element; + switch (index) { + case 0: + return new StyledString(TextFormatter.getLabelName(label)); + case 1: + return new StyledString(TextFormatter.getLabelValue(label, false)); + default: + } + } + return null; + } + + @Override + protected Image getColumnImage(Object element, int index) { + if (index == 0 && element instanceof AbstractStorageLabel) { + return ImageFormatter.getImageForLabel(((AbstractStorageLabel) element).getStorageLabelType()); + } + return null; + } + }); + labelsTableViewer.setComparator(new ViewerComparator() { + @Override + public int compare(Viewer viewer, Object e1, Object e2) { + if (e1 instanceof AbstractStorageLabel && e2 instanceof AbstractStorageLabel) { + return ((AbstractStorageLabel) e1).compareTo((AbstractStorageLabel) e2); + } + return super.compare(viewer, e1, e2); + } + }); + labelsTableViewer.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + if (labelsTableViewer.getSelection().isEmpty() || !isRemoteStorageDisplayed()) { + removeLabels.setEnabled(false); + } else { + removeLabels.setEnabled(true); + } + } + + }); + + addNewLabel = toolkit.createButton(labelComposite, "Add", SWT.PUSH); + addNewLabel.setToolTipText("Add new label(s)"); + addNewLabel.setEnabled(false); + addNewLabel.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + IHandlerService handlerService = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); + ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + + Command command = commandService.getCommand(AddStorageLabelHandler.COMMAND); + ExecutionEvent executionEvent = handlerService.createExecutionEvent(command, new Event()); + try { + command.executeWithChecks(executionEvent); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } + }); + + removeLabels = toolkit.createButton(labelComposite, "Remove", SWT.PUSH); + removeLabels.setToolTipText("Remove selected label(s)"); + removeLabels.setEnabled(false); + removeLabels.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + if (!labelsTableViewer.getSelection().isEmpty()) { + List> inputList = new ArrayList>(); + for (Object object : ((StructuredSelection) labelsTableViewer.getSelection()).toArray()) { + if (object instanceof AbstractStorageLabel) { + inputList.add((AbstractStorageLabel) object); + } + } + + IHandlerService handlerService = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); + ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + + Command command = commandService.getCommand(RemoveStorageLabelHandler.COMMAND); + ExecutionEvent executionEvent = handlerService.createExecutionEvent(command, new Event()); + IEvaluationContext context = (IEvaluationContext) executionEvent.getApplicationContext(); + context.addVariable(RemoveStorageLabelHandler.INPUT, inputList); + try { + command.executeWithChecks(executionEvent); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } + } + }); + + labelSection.setClient(labelComposite); + labelSection.setLayout(new TableWrapLayout()); + labelSection.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); + // END - General section + + refreshData(); + } + + /** + * Sets layout data for the form. + * + * @param layoutData + * LayoutData. + */ + public void setLayoutData(Object layoutData) { + form.setLayoutData(layoutData); + } + + /** + * {@inheritDoc} + */ + @Override + public void selectionChanged(SelectionChangedEvent event) { + ISelection selection = event.getSelection(); + if (!selection.isEmpty()) { + if (selection instanceof StructuredSelection) { + StructuredSelection structuredSelection = (StructuredSelection) selection; + Object firstElement = structuredSelection.getFirstElement(); + if (firstElement instanceof IStorageDataProvider) { + if (!ObjectUtils.equals(storageDataProvider, firstElement)) { + storageDataProvider = (IStorageDataProvider) firstElement; + storageData = storageDataProvider.getStorageData(); + final CmrRepositoryDefinition cmrRepositoryDefinition = storageDataProvider.getCmrRepositoryDefinition(); + LabelValueEditingSupport editingSupport = new LabelValueEditingSupport(labelsTableViewer, storageDataProvider.getStorageData(), cmrRepositoryDefinition); + editingSupport.addLabelEditListener(new LabelEditListener() { + + @Override + public void preLabelValueChange(AbstractStorageLabel label) { + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + try { + cmrRepositoryDefinition.getStorageService().removeLabelFromStorage(storageDataProvider.getStorageData(), label); + } catch (StorageException e) { + InspectIT.getDefault().createErrorDialog("Label value can not be updated.", e, -1); + } + } + } + + @Override + public void postLabelValueChange(AbstractStorageLabel label) { + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + try { + label.setId(0); + cmrRepositoryDefinition.getStorageService().addLabelToStorage(storageDataProvider.getStorageData(), label, true); + refreshStorageManagerView(cmrRepositoryDefinition); + } catch (StorageException e) { + InspectIT.getDefault().createErrorDialog("Label value can not be updated.", e, -1); + } + } + } + + }); + valueViewerColumn.setEditingSupport(editingSupport); + refreshData(); + } + return; + } else if (firstElement instanceof ILocalStorageDataProvider) { + IStorageData localStorageData = ((ILocalStorageDataProvider) firstElement).getLocalStorageData(); + if (!ObjectUtils.equals(storageData, localStorageData)) { + storageDataProvider = null; // NOPMD + storageData = localStorageData; + valueViewerColumn.setEditingSupport(null); + refreshData(); + } + return; + } + } + } + if (null != storageDataProvider || null != storageData) { + storageDataProvider = null; // NOPMD + storageData = null; // NOPMD + valueViewerColumn.setEditingSupport(null); + refreshData(); + } + } + + /** + * Refresh the data after selection is changed. + */ + private void refreshData() { + // refresh data asynchronously + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + form.setBusy(true); + if (isDataExistsForDisplay()) { + // data exists, we display info we have + form.setText(storageData.getName()); + form.setMessage(null, IMessageProvider.NONE); + mainComposite.setVisible(true); + uniqueId.setText(storageData.getId()); + String desc = storageData.getDescription(); + if (null != desc) { + if (desc.length() > MAX_DESCRIPTION_LENGTH) { + description.setText("

" + desc.substring(0, MAX_DESCRIPTION_LENGTH) + ".. [More]

", true, false); + } else { + description.setText(desc, false, false); + } + } else { + description.setText("", false, false); + } + sizeOnDisk.setText(NumberFormatter.humanReadableByteCount(storageData.getDiskSize())); + labelsTableViewer.setInput(storageData.getLabelList()); + labelsTableViewer.refresh(); + addNewLabel.setEnabled(isRemoteStorageDisplayed()); + + // depending of type enable/disable widgets + if (isRemoteStorageDisplayed()) { + // for remote storage + CmrRepositoryDefinition cmrRepositoryDefinition = storageDataProvider.getCmrRepositoryDefinition(); + repository.setText(cmrRepositoryDefinition.getName() + " (" + cmrRepositoryDefinition.getIp() + ":" + cmrRepositoryDefinition.getPort() + ")"); + state.setText(TextFormatter.getStorageStateTextualRepresentation(storageDataProvider.getStorageData().getState())); + Image img = ImageFormatter.getImageForStorageLeaf((StorageData) storageData); + form.setImage(img); + } else { + // for downloaded storage + repository.setText("Available locally"); + state.setText("Downloaded"); + form.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_STORAGE_DOWNLOADED)); + } + } else { + // nothing is selected we display the proper info + form.setText(null); + form.setMessage("Please select a storage to see its properties.", IMessageProvider.INFORMATION); + mainComposite.setVisible(false); + } + + form.getBody().layout(true, true); + form.setBusy(false); + } + }); + } + + /** + * Shows storage description box. + */ + private void showStorageDescriptionBox() { + int shellStyle = SWT.CLOSE | SWT.TITLE | SWT.BORDER | SWT.APPLICATION_MODAL | SWT.RESIZE; + PopupDialog popupDialog = new PopupDialog(form.getShell(), shellStyle, true, false, false, false, false, "Storage description", "Storage description") { + private static final int CURSOR_SIZE = 15; + + @Override + protected Control createDialogArea(Composite parent) { + Composite composite = (Composite) super.createDialogArea(parent); + Text text = toolkit.createText(parent, null, SWT.MULTI | SWT.READ_ONLY | SWT.WRAP | SWT.H_SCROLL | SWT.V_SCROLL); + GridData gd = new GridData(GridData.BEGINNING | GridData.FILL_BOTH); + gd.horizontalIndent = 3; + gd.verticalIndent = 3; + text.setLayoutData(gd); + text.setText(storageData.getDescription()); + return composite; + } + + @Override + protected Point getInitialLocation(Point initialSize) { + // show popup relative to cursor + Display display = getShell().getDisplay(); + Point location = display.getCursorLocation(); + location.x += CURSOR_SIZE; + location.y += CURSOR_SIZE; + return location; + } + + @Override + protected Point getInitialSize() { + return new Point(400, 200); + } + }; + popupDialog.open(); + } + + /** + * Refreshes the {@link StorageManagerView}, but only reloads the storages from given + * repository. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + */ + private void refreshStorageManagerView(CmrRepositoryDefinition cmrRepositoryDefinition) { + IViewPart viewPart = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().findView(StorageManagerView.VIEW_ID); + if (viewPart instanceof StorageManagerView) { + ((StorageManagerView) viewPart).refresh(cmrRepositoryDefinition); + } + } + + /** + * @return Returns if any data exists for displaying. + */ + private boolean isDataExistsForDisplay() { + return null != storageData; + } + + /** + * @return Returns if the remote storage is displayed. + */ + private boolean isRemoteStorageDisplayed() { + return null != storageDataProvider; + } + + /** + * @return If form is disposed. + */ + public boolean isDisposed() { + return form.isDisposed(); + } + + /** + * Disposes the form. + */ + public void dispose() { + form.dispose(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/formatter/ColorFormatter.java b/inspectIT/src/info/novatec/inspectit/rcp/formatter/ColorFormatter.java new file mode 100644 index 000000000..a2e405a0b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/formatter/ColorFormatter.java @@ -0,0 +1,109 @@ +package info.novatec.inspectit.rcp.formatter; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.resource.ResourceManager; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.RGB; + +/** + * The class provide different methods for color manipulation. + * + * @author Ivan Senic + * + */ +public final class ColorFormatter { + + /** + * Private constructor. + */ + private ColorFormatter() { + } + + /** + * Creates the color that is between two supplied color descriptors in linear gradient. + * + * @param rgb1 + * {@link RGB} descriptor for first (starting) color. + * @param rgb2 + * {@link RGB} descriptor for second (ending) color. + * @param ratio + * Ratio should be number from 0 to 1 (including). The numbers closer to 0 will + * favorite the first (starting) color, while the numbers closer to 1 will favorite + * the second (ending) color. + * @param resourceManager + * {@link ResourceManager} that color will be created with. Note that is + * responsibility of caller to handle the disposal of the resource manager. + * @return {@link Color} + */ + public static Color getLinearGradientColor(RGB rgb1, RGB rgb2, double ratio, ResourceManager resourceManager) { + Assert.isTrue(ratio >= 0 && ratio <= 1, "Ratio for linear gradient must me between 0 and 1 (including)."); + int red = (int) (rgb2.red * ratio + rgb1.red * (1 - ratio)); + int green = (int) (rgb2.green * ratio + rgb1.green * (1 - ratio)); + int blue = (int) (rgb2.blue * ratio + rgb1.blue * (1 - ratio)); + RGB newRgb = new RGB(red, green, blue); + return resourceManager.createColor(newRgb); + } + + /** + * Returns the so-called performance color. This color is actually a color between good - + * average - bad color descriptors based on the values supplied. For example, if the actual + * value is close to the good value, good color will be returned. Color is created based on a + * linear gradient between two colors. It is possible that actual value is "better" than good + * value, and in this case always the good color is returned. It is irrelevant if the good value + * is higher or smaller that bad value, but it is important that they are not the same + * (exception will be thrown in that case). + * + * @param goodRgb + * {@link RGB} that defines a color that should be returned if performance is good. + * @param avgRgb + * {@link RGB} that defines a color that should be returned if performance is + * average. + * @param badRgb + * {@link RGB} that defines a color that should be returned if performance is bad. + * @param actualValue + * Actual value. + * @param goodValue + * Good value. + * @param badValue + * Bad value. + * @param resourceManager + * {@link ResourceManager} that color will be created with. Note that is + * responsibility of caller to handle the disposal of the resource manager. + * @return {@link Color}. + */ + public static Color getPerformanceColor(RGB goodRgb, RGB avgRgb, RGB badRgb, double actualValue, double goodValue, double badValue, ResourceManager resourceManager) { + Assert.isTrue(goodValue != badValue); + double avg = (goodValue + badValue) / 2; + if (goodValue > badValue) { + if (actualValue > goodValue) { + return resourceManager.createColor(goodRgb); + } else if (actualValue < badValue) { + return resourceManager.createColor(badRgb); + } else if (actualValue > avg) { + // return combination of green and yellow + double factor = Math.abs((actualValue - avg) / (goodValue - avg)); + return getLinearGradientColor(avgRgb, goodRgb, factor, resourceManager); + } else { + // return combination of red and yellow + double factor = Math.abs((actualValue - badValue) / (avg - badValue)); + return getLinearGradientColor(badRgb, avgRgb, factor, resourceManager); + } + } else if (goodValue < badValue) { + if (actualValue < goodValue) { + return resourceManager.createColor(goodRgb); + } else if (actualValue > badValue) { + return resourceManager.createColor(badRgb); + } else if (actualValue < avg) { + // return combination of green and yellow + double factor = 1 - Math.abs((actualValue - goodValue) / (avg - goodValue)); + return ColorFormatter.getLinearGradientColor(avgRgb, goodRgb, factor, resourceManager); + } else { + // return combination of red and yellow + double factor = 1 - Math.abs((actualValue - avg) / (badValue - avg)); + return ColorFormatter.getLinearGradientColor(badRgb, avgRgb, factor, resourceManager); + } + } else { + throw new RuntimeException("Performance color can not be created due to the bad input values."); + } + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/formatter/ImageFormatter.java b/inspectIT/src/info/novatec/inspectit/rcp/formatter/ImageFormatter.java new file mode 100644 index 000000000..b13f2f89b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/formatter/ImageFormatter.java @@ -0,0 +1,289 @@ +package info.novatec.inspectit.rcp.formatter; + +import info.novatec.inspectit.communication.data.cmr.AgentStatusData; +import info.novatec.inspectit.communication.data.cmr.WritingStatus; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; +import info.novatec.inspectit.rcp.repository.StorageRepositoryDefinition; +import info.novatec.inspectit.rcp.resource.CombinedIcon; +import info.novatec.inspectit.storage.LocalStorageData; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageData.StorageState; +import info.novatec.inspectit.storage.label.type.AbstractCustomStorageLabelType; +import info.novatec.inspectit.storage.label.type.AbstractStorageLabelType; +import info.novatec.inspectit.storage.label.type.impl.AssigneeLabelType; +import info.novatec.inspectit.storage.label.type.impl.CreationDateLabelType; +import info.novatec.inspectit.storage.label.type.impl.DataTimeFrameLabelType; +import info.novatec.inspectit.storage.label.type.impl.ExploredByLabelType; +import info.novatec.inspectit.storage.label.type.impl.RatingLabelType; +import info.novatec.inspectit.storage.label.type.impl.StatusLabelType; +import info.novatec.inspectit.storage.label.type.impl.UseCaseLabelType; + +import java.util.Date; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.ResourceManager; +import org.eclipse.jface.viewers.DecorationOverlayIcon; +import org.eclipse.swt.graphics.Image; + +/** + * The class provide image descriptors for the different elements. + * + * @author Ivan Senic + * + */ +public final class ImageFormatter { + + /** + * List of icons that can be used for custom labels. + */ + public static final String[] LABEL_ICONS = new String[] { InspectITImages.IMG_HOME, InspectITImages.IMG_MESSAGE, InspectITImages.IMG_WARNING, InspectITImages.IMG_ACTIVITY, + InspectITImages.IMG_CALENDAR, InspectITImages.IMG_ASSIGNEE_LABEL_ICON, InspectITImages.IMG_CHECKMARK, InspectITImages.IMG_CLASS, InspectITImages.IMG_PACKAGE, + InspectITImages.IMG_METHOD_DEFAULT, InspectITImages.IMG_MEMORY_OVERVIEW, InspectITImages.IMG_CPU_OVERVIEW, InspectITImages.IMG_THREADS_OVERVIEW, InspectITImages.IMG_DATABASE, + InspectITImages.IMG_DATE_LABEL_ICON, InspectITImages.IMG_USECASE_LABEL_ICON, InspectITImages.IMG_RATING_LABEL_ICON, InspectITImages.IMG_STATUS_LABEL_ICON, InspectITImages.IMG_SEARCH, + InspectITImages.IMG_TIMER, InspectITImages.IMG_TOOL, InspectITImages.IMG_TRASH, InspectITImages.IMG_PROPERTIES, InspectITImages.IMG_TIME, InspectITImages.IMG_FONT, + InspectITImages.IMG_INFORMATION, InspectITImages.IMG_FILTER, InspectITImages.IMG_HTTP_URL }; + + /** + * Private constructor. + */ + private ImageFormatter() { + } + + /** + * Returns the {@link Image} for the composite that represents a label. + * + * @param labelType + * Label type. + * @return {@link Image} for Composite. + */ + public static Image getImageForLabel(AbstractStorageLabelType labelType) { + return InspectIT.getDefault().getImage(getImageKeyForLabel(labelType)); + } + + /** + * Returns the {@link ImageDescriptor} for the composite that represents a label. + * + * @param labelType + * Label type. + * @return {@link ImageDescriptor} for {@link Composite}. + */ + public static ImageDescriptor getImageDescriptorForLabel(AbstractStorageLabelType labelType) { + return InspectIT.getDefault().getImageDescriptor(getImageKeyForLabel(labelType)); + } + + /** + * Returns the image key for the label type. + * + * @param labelType + * Label type. + * @return String that represents the image key. Will never be null. + */ + private static String getImageKeyForLabel(AbstractStorageLabelType labelType) { + if (AssigneeLabelType.class.equals(labelType.getClass())) { + return InspectITImages.IMG_ASSIGNEE_LABEL_ICON; + } else if (CreationDateLabelType.class.equals(labelType.getClass())) { + return InspectITImages.IMG_DATE_LABEL_ICON; + } else if (ExploredByLabelType.class.equals(labelType.getClass())) { + return InspectITImages.IMG_MOUNTEDBY_LABEL_ICON; + } else if (RatingLabelType.class.equals(labelType.getClass())) { + return InspectITImages.IMG_RATING_LABEL_ICON; + } else if (StatusLabelType.class.equals(labelType.getClass())) { + return InspectITImages.IMG_STATUS_LABEL_ICON; + } else if (UseCaseLabelType.class.equals(labelType.getClass())) { + return InspectITImages.IMG_USECASE_LABEL_ICON; + } else if (DataTimeFrameLabelType.class.equals(labelType.getClass())) { + return InspectITImages.IMG_TIMEFRAME; + } else if (labelType instanceof AbstractCustomStorageLabelType) { + AbstractCustomStorageLabelType customLabelType = (AbstractCustomStorageLabelType) labelType; + if (null != customLabelType.getImageKey()) { + // assure that the image key is registered in the image registry + if (null != InspectIT.getDefault().getImage(customLabelType.getImageKey())) { // NOPMD + return customLabelType.getImageKey(); + } + } + if (Boolean.class.equals(customLabelType.getValueClass())) { + return InspectITImages.IMG_CHECKMARK; + } else if (Date.class.equals(customLabelType.getValueClass())) { + return InspectITImages.IMG_CALENDAR; + } else if (Number.class.equals(customLabelType.getValueClass())) { + return InspectITImages.IMG_NUMBER; + } else if (String.class.equals(customLabelType.getValueClass())) { + return InspectITImages.IMG_FONT; + } + } + return InspectITImages.IMG_USER_LABEL_ICON; + } + + /** + * + * @param storageData + * {@link StorageData} to get picture for. + * @return Returns the {@link Image} for the storage, based on the + * {@link StorageData.StorageState}. + */ + public static Image getImageForStorageLeaf(StorageData storageData) { + if (storageData.getState() == StorageState.CREATED_NOT_OPENED) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_STORAGE); + } else if (storageData.getState() == StorageState.OPENED) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_STORAGE_OPENED); + } else if (storageData.getState() == StorageState.RECORDING) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_STORAGE_RECORDING); + } else if (storageData.getState() == StorageState.CLOSED) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_STORAGE_CLOSED); + } + return null; + } + + /** + * Returns image based on the CMR repository status. + * + * @param selectedCmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + * @param small + * Should picture be small or big. + * @return Image. + */ + public static Image getCmrRepositoryImage(CmrRepositoryDefinition selectedCmrRepositoryDefinition, boolean small) { + if (selectedCmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.ONLINE) { + if (small) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_SERVER_ONLINE_SMALL); + } else { + return InspectIT.getDefault().getImage(InspectITImages.IMG_SERVER_ONLINE); + } + } else if (selectedCmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.OFFLINE) { + if (small) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_SERVER_OFFLINE_SMALL); + } else { + return InspectIT.getDefault().getImage(InspectITImages.IMG_SERVER_OFFLINE); + } + } else { + if (small) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_SERVER_REFRESH_SMALL); + } else { + return InspectIT.getDefault().getImage(InspectITImages.IMG_SERVER_REFRESH); + } + } + } + + /** + * Returns image for the title box. + * + * @param storageRepositoryDefinition + * {@link StorageRepositoryDefinition} + * @return Image for the title box. + */ + public static Image getStorageRepositoryImage(StorageRepositoryDefinition storageRepositoryDefinition) { + LocalStorageData localStorageData = storageRepositoryDefinition.getLocalStorageData(); + if (localStorageData.isFullyDownloaded()) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_STORAGE_DOWNLOADED); + } else if (storageRepositoryDefinition.getCmrRepositoryDefinition().getOnlineStatus() != OnlineStatus.OFFLINE) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_STORAGE_AVAILABLE); + } else { + return InspectIT.getDefault().getImage(InspectITImages.IMG_STORAGE_NOT_AVAILABLE); + } + } + + /** + * Returns image that represents the {@link WritingStatus} or null if the writing status passed + * is null. + * + * @param status + * Image for {@link WritingStatus}. + * @return Returns image that represents the {@link WritingStatus} or null if the writing status + * passed is null. + */ + public static Image getWritingStatusImage(WritingStatus status) { + if (null == status) { + return null; + } + switch (status) { + case GOOD: + return InspectIT.getDefault().getImage(InspectITImages.IMG_FLAG); + case MEDIUM: + return InspectIT.getDefault().getImage(InspectITImages.IMG_WARNING); + case BAD: + return InspectIT.getDefault().getImage(InspectITImages.IMG_ALERT); + default: + return null; + } + } + + /** + * Returns overlayed icon for editors with additional {@link ImageDescriptor} depending if the + * repository is CMR or Storage repository. + * + * @param original + * Original icon. + * @param repositoryDefinition + * Repository definition. + * @param resourceManager + * Resource manager that image will be created with. It is responsibility of a caller + * to provide {@link ResourceManager} for correct image disposing. + * @return Overlayed icon. + */ + public static Image getOverlayedEditorImage(Image original, RepositoryDefinition repositoryDefinition, ResourceManager resourceManager) { + if (repositoryDefinition instanceof CmrRepositoryDefinition) { + return original; + } else if (repositoryDefinition instanceof StorageRepositoryDefinition) { + ImageDescriptor overlayDescriptor = InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_STORAGE_OVERLAY); + DecorationOverlayIcon icon = new DecorationOverlayIcon(original, new ImageDescriptor[] { null, null, null, overlayDescriptor, null }); + Image img = resourceManager.createImage(icon); + return img; + } + return null; + } + + /** + * Returns the combined image for given array of descriptors. Orientation can be vertical or + * horizontal. + * + * @param resourceManager + * {@link ResourceManager}. + * @param orientation + * SWT#Vertical or SWT#Horizontal. Descriptors will be passed in given order. + * @param descriptors + * Array of descriptors. + * + * @return Combined {@link Image}. + */ + public static Image getCombinedImage(ResourceManager resourceManager, int orientation, ImageDescriptor... descriptors) { + ImageDescriptor combinedImageDescriptor = new CombinedIcon(descriptors, orientation); + Image img = resourceManager.createImage(combinedImageDescriptor); + return img; + } + + /** + * Returns the image for the agent based on the last data sent date. + * + * @param agentStatusData + * {@link AgentStatusData} golding the information or null if it's not available. + * + * @return {@link Image} + */ + public static Image getAgentImage(AgentStatusData agentStatusData) { + if (null != agentStatusData) { + switch (agentStatusData.getAgentConnection()) { + case CONNECTED: + if (null != agentStatusData.getMillisSinceLastData()) { + long millis = agentStatusData.getMillisSinceLastData().longValue(); + // at last one minute of not sending data to display as the non active + if (millis > 60000) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_AGENT_NOT_SENDING); + } else { + return InspectIT.getDefault().getImage(InspectITImages.IMG_AGENT_ACTIVE); + } + } else { + return InspectIT.getDefault().getImage(InspectITImages.IMG_AGENT_NOT_SENDING); + } + default: + return InspectIT.getDefault().getImage(InspectITImages.IMG_AGENT_NOT_ACTIVE); + } + } else { + return InspectIT.getDefault().getImage(InspectITImages.IMG_AGENT_NOT_ACTIVE); + } + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/formatter/NumberFormatter.java b/inspectIT/src/info/novatec/inspectit/rcp/formatter/NumberFormatter.java new file mode 100644 index 000000000..658edb754 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/formatter/NumberFormatter.java @@ -0,0 +1,338 @@ +package info.novatec.inspectit.rcp.formatter; + +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +/** + * This class is for formatting some output. + * + * @author Eduard Tudenhoefner + * + */ +public final class NumberFormatter { + + /** + * Formats a decimal number with the specific pattern. + */ + private static DecimalFormat decFormat = new DecimalFormat("###0.##"); + + /** + * Formats a decimal number and returns it in milliseconds format. + */ + private static DecimalFormat millisFormat = new DecimalFormat("0.00"); + + /** + * Formats a decimal number and returns it in nanoseconds format. + */ + private static DecimalFormat nanosFormat = new DecimalFormat("0.00"); + + /** + * Formats a decimal number with the specified pattern. + */ + private static DecimalFormat cpuFormat = new DecimalFormat("#.##"); + + /** + * Formats a decimal number with the specified pattern. + */ + private static DecimalFormat intFormat = new DecimalFormat("###"); + + /** + * Formats a decimal number with the specified pattern. + */ + private static DecimalFormat doubleFormat = new DecimalFormat("0.000"); + + /** + * Formats a decimal number without the specified pattern. + */ + private static DecimalFormat doubleUnspecificFormat = new DecimalFormat(); + + /** + * Formats a date/time value with the specified pattern. + */ + private static DateFormat dateMillisFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss.SSS"); + + /** + * Formats a date/time value with the specified pattern. + */ + private static DateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); + + static { + doubleUnspecificFormat.setGroupingSize(0); + } + + /** + * The default private constructor. + */ + private NumberFormatter() { + } + + /** + * Converts time in milliseconds to a String in the format HH:mm:ss. + * + * @param time + * the time in milliseconds. + * @return a String representing the time in the format HH:mm:ss. + */ + public static String millisecondsToString(long time) { + int seconds = (int) (time / 1000 % 60); + int minutes = (int) ((time / 60000) % 60); + int hours = (int) ((time / 3600000) % 24); + int days = (int) ((time / 3600000) / 24); + + StringBuilder builder = new StringBuilder(); + builder.append(days); + builder.append("d "); + builder.append(hours); + builder.append("h "); + builder.append(minutes); + builder.append("m "); + builder.append(seconds); + builder.append('s'); + + return builder.toString(); + } + + /** + * Formats the time to a String value with milliseconds. + * + * @param time + * The time as long value. + * @return The formatted string. + */ + public static String formatTimeWithMillis(long time) { + return formatTimeWithMillis(new Date(time)); + } + + /** + * Formats the time to a String value with milliseconds. + * + * @param date + * The date to format. + * @return The formatted string. + */ + public static String formatTimeWithMillis(Date date) { + synchronized (dateMillisFormat) { + return dateMillisFormat.format(date); + } + } + + /** + * Formats the time to a String value. + * + * @param time + * The time as long value. + * @return The formatted string. + */ + public static String formatTime(long time) { + return formatTime(new Date(time)); + } + + /** + * Formats the time to a String value. + * + * @param date + * The date to format. + * @return The formatted string. + */ + public static String formatTime(Date date) { + synchronized (dateFormat) { + return dateFormat.format(date); + } + } + + /** + * Formats nanoseconds to seconds. + * + * @param time + * The time as long value. + * @return A formatted string. + */ + public static String formatNanosToSeconds(long time) { + double sec = time / 1000000000d; + return nanosFormat.format(sec) + " s"; + } + + /** + * Formats milliseconds to seconds. + * + * @param time + * The time as long value. + * @return A formatted string. + */ + public static String formatMillisToSeconds(long time) { + double sec = time / 1000d; + return millisFormat.format(sec) + " s"; + } + + /** + * Formats bytes to kiloBytes. + * + * @param bytes + * The bytes to format. + * @return A formatted string. + */ + public static String formatBytesToKBytes(long bytes) { + return decFormat.format((double) bytes / 1024) + " Kb"; + } + + /** + * Formats bytes to megaBytes. + * + * @param bytes + * The bytes to format. + * @return A formatted string. + */ + public static String formatBytesToMBytes(long bytes) { + return decFormat.format((double) bytes / (1024 * 1024)) + " Mb"; + } + + /** + * Adds a %-sign to a floating number. For example: input = 12 / output = 12 %. + * + * @param percent + * The value to format. + * @return The formatted string. + */ + public static String formatCpuPercent(float percent) { + return cpuFormat.format(percent) + " %"; + } + + /** + * Formats an integer value. For example: input = 1234567 / output = 1,234,567. + * + * @param number + * The value to format. + * @return The formatted String. + */ + public static String formatInteger(int number) { + return intFormat.format(number); + } + + /** + * Formats a long value. For example: input = 1234567 / output = 1,234,567. + * + * @param number + * The value to format. + * @return The formatted string. + */ + public static String formatLong(long number) { + return intFormat.format(number); + } + + /** + * Formats a double value. For example: input = 123545.9876543 / output = 123545.987. + * + * @param number + * The value to format. + * @return The formatted string. + */ + public static String formatDouble(double number) { + return doubleFormat.format(number); + } + + /** + * Formats a double value based on the number of decimal places. + * + * @param number + * The value to format. + * @param decimalPlaces + * Number of decimal places. + * @return The formatted string. + */ + public static String formatDouble(double number, int decimalPlaces) { + doubleUnspecificFormat.setMaximumFractionDigits(decimalPlaces); + doubleUnspecificFormat.setMinimumFractionDigits(decimalPlaces); + return doubleUnspecificFormat.format(number); + } + + /** + * Returns the human readable bytes number. + *

+ * IMPORTANT: The method code is copied/taken/based from stackoverflow. Original author is aioobe. License info can be found here. + * + * @param bytes + * Bytes to transform. + * @return Human readable string. + */ + public static String humanReadableByteCount(long bytes) { + int unit = 1024; + if (bytes < unit) { + return bytes + " B"; + } else { + int exp = (int) (Math.log(bytes) / Math.log(unit)); + String pre = ("KMGTPE").charAt(exp - 1) + "i"; + return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); + } + } + + /** + * Returns the human readable millis count. + * + * If shortDescription is true then the format will be: x + * days/hours/minutes/seconds depending on the time. Meaning if more than day has passed only 1 + * day will be returned. Otherwise if between 23-24 hours passed, 23 hours will be returned. + * + * If shortDescrption is false then the the returned format is xd, xh, xm, + * xs.. Not that if any unit is 0 it won't be printed. + * + * @param millis + * Number of milliseconds. + * @param shortDescription + * Short or long description. + * @return Formated string. + */ + public static String humanReadableMillisCount(long millis, boolean shortDescription) { + StringBuilder stringBuilder = new StringBuilder(); + boolean started = false; + + long days = TimeUnit.MILLISECONDS.toDays(millis); + if (days > 0) { + if (shortDescription) { + return days + " day" + (days > 1 ? "s" : ""); + } + stringBuilder.append(String.format("%dd", days)); + started = true; + } + + long hours = TimeUnit.MILLISECONDS.toHours(millis) - TimeUnit.DAYS.toHours(TimeUnit.MILLISECONDS.toDays(millis)); + if (started) { + stringBuilder.append(String.format(" %dh", hours)); + } else if (hours > 0) { + if (shortDescription) { + return hours + " hour" + (hours > 1 ? "s" : ""); + } + stringBuilder.append(String.format("%dh", hours)); + started = true; + } + + long min = TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis)); + if (started) { + stringBuilder.append(String.format(" %dm", min)); + } else if (min > 0) { + if (shortDescription) { + return min + " minute" + (min > 1 ? "s" : ""); + } + stringBuilder.append(String.format("%dm", min)); + started = true; + } + + long sec = TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)); + if (started) { + stringBuilder.append(String.format(" %ds", sec)); + } else if (sec > 0) { + if (shortDescription) { + return sec + " second" + (sec > 1 ? "s" : ""); + } + stringBuilder.append(String.format("%ds", sec)); + started = true; + } + + return stringBuilder.toString(); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/formatter/SensorTypeAvailabilityEnum.java b/inspectIT/src/info/novatec/inspectit/rcp/formatter/SensorTypeAvailabilityEnum.java new file mode 100644 index 000000000..bc75c0188 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/formatter/SensorTypeAvailabilityEnum.java @@ -0,0 +1,78 @@ +package info.novatec.inspectit.rcp.formatter; + +/** + * This enumeration contains strings/tooltips for the availability of different sensor-types. + * + * @author Eduard Tudenhoefner + * + */ +public enum SensorTypeAvailabilityEnum { + + /** + * Tooltip for SystemInformation sensor-type. + */ + SYS_INF_NA("This item is not available. You have to activate the 'SystemInformation' platform sensor type in 'inspectit-agent.cfg' for viewing this information."), + + /** + * Tooltip for CpuInformation sensor-type. + */ + CPU_INF_NA("This item is not available. You have to activate the 'CpuInformation' platform sensor type in 'inspectit-agent.cfg' for viewing this information."), + + /** + * Tooltip for ClassLoadingInformation sensor-type. + */ + CLASS_INF_NA("This item is not available. You have to activate the 'ClassLoadingInformation' platform sensor type in 'inspectit-agent.cfg' for viewing this information."), + + /** + * Tooltip for ThreadInformation sensor-type. + */ + THREAD_INF_NA("This item is not available. You have to activate the 'ThreadInformation' platform sensor type in 'inspectit-agent.cfg' for viewing this information."), + + /** + * Tooltip for RuntimeInformation sensor-type. + */ + RUNTIME_INF_NA("This item is not available. You have to activate the 'RuntimeInformation' platform sensor type in 'inspectit-agent.cfg' for viewing this information."), + + /** + * Tooltip for MemoryInformation sensor-type. + */ + MEMORY_INF_NA("This item is not available. You have to activate the 'MemoryInformation' platform sensor type in 'inspectit-agent.cfg' for viewing this information."), + + /** + * Tooltip for CompilationInformation sensor-type. + */ + COMPILATION_INF_NA("This item is not available. You have to activate the 'CompilationInformation' platform sensor type in 'inspectit-agent.cfg' for viewing this information."), + + /** + * Tooltip when no sensor-type is available. + */ + SENSOR_NA("This item is not available. You have to activate at least one platform sensor type in 'inspectit-agent.cfg' for viewing this information."), + + /** + * Tooltip when no exception sensor is available. + */ + EXCEPTION_SENSOR_NA("This item is not available. You have to activate the exception sensor in 'inspectit-agent.cfg' for viewing this information."); + + /** + * The message string. + */ + private String message; + + /** + * + * @param message + * The error message. + */ + private SensorTypeAvailabilityEnum(String message) { + this.message = message; + } + + /** + * Returns the message string. + * + * @return The message string. + */ + public String getMessage() { + return message; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/formatter/TextFormatter.java b/inspectIT/src/info/novatec/inspectit/rcp/formatter/TextFormatter.java new file mode 100644 index 000000000..2e3163a70 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/formatter/TextFormatter.java @@ -0,0 +1,602 @@ +package info.novatec.inspectit.rcp.formatter; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.InvocationAwareData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.communication.data.cmr.AgentStatusData; +import info.novatec.inspectit.communication.data.cmr.WritingStatus; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.model.AgentFolderFactory; +import info.novatec.inspectit.rcp.model.AgentLeaf; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; +import info.novatec.inspectit.rcp.util.data.RegExAggregatedHttpTimerData; +import info.novatec.inspectit.storage.LocalStorageData; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageData.StorageState; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.label.BooleanStorageLabel; +import info.novatec.inspectit.storage.label.type.AbstractCustomStorageLabelType; +import info.novatec.inspectit.storage.label.type.AbstractStorageLabelType; +import info.novatec.inspectit.storage.label.type.impl.AssigneeLabelType; +import info.novatec.inspectit.storage.label.type.impl.CreationDateLabelType; +import info.novatec.inspectit.storage.label.type.impl.DataTimeFrameLabelType; +import info.novatec.inspectit.storage.label.type.impl.ExploredByLabelType; +import info.novatec.inspectit.storage.label.type.impl.RatingLabelType; +import info.novatec.inspectit.storage.label.type.impl.StatusLabelType; +import info.novatec.inspectit.storage.label.type.impl.UseCaseLabelType; + +import java.text.DateFormat; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.jface.preference.JFacePreferences; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.StyledString.Styler; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.TextStyle; + +/** + * This class provides some static methods to create some common {@link String} and + * {@link StyledString} objects. + * + * @author Patrice Bouillet + * @author Stefan Siegl + * @author Ivan Senic + */ +public final class TextFormatter { + + /** Logical Name for the font used for the error marker. */ + public static final String FONT_ERROR_MARKER = "de.inspectit.font.errormarker"; + + /** + * Default size of the font used in the error marker. This will be used if the size of default + * system font can not be read. + */ + public static final int DEFAULT_FONT_ERROR_SIZE = 10; + + static { + FontData[] fontData = JFaceResources.getDefaultFontDescriptor().getFontData(); + if (fontData.length > 0) { + FontData defaultFontData = fontData[0]; + int height = (int) defaultFontData.height; + JFaceResources.getFontRegistry().put(FONT_ERROR_MARKER, new FontData[] { new FontData("Arial", height, SWT.BOLD | SWT.ITALIC) }); + } else { + JFaceResources.getFontRegistry().put(FONT_ERROR_MARKER, new FontData[] { new FontData("Arial", DEFAULT_FONT_ERROR_SIZE, SWT.BOLD | SWT.ITALIC) }); + } + } + + /** + * Private constructor. Prevent instantiation. + */ + private TextFormatter() { + } + + /** + * Returns a Styled String out of the {@link MethodIdent} objects which looks like: + * 'name'('parameter') - 'package'.'class'. Additionally, as this returns a {@link StyledString} + * , the last part is colored. + * + * @param methodIdent + * The object which contains the information to create the styled method string. + * @return The created styled method string. + */ + public static StyledString getStyledMethodString(MethodIdent methodIdent) { + StyledString styledString = new StyledString(); + + styledString.append(getMethodWithParameters(methodIdent)); + String decoration; + if (methodIdent.getPackageName() != null && !methodIdent.getPackageName().equals("")) { + decoration = MessageFormat.format("- {0}.{1}", new Object[] { methodIdent.getPackageName(), methodIdent.getClassName() }); + } else { + decoration = MessageFormat.format("- {0}", new Object[] { methodIdent.getClassName() }); + } + + styledString.append(decoration, StyledString.QUALIFIER_STYLER); + + return styledString; + } + + /** + * Returns a method string which is appended by the parameters. + * + * @param methodIdent + * The object which contains the information to create the styled method string. + * @return The created method + parameters string. + */ + public static String getMethodWithParameters(MethodIdent methodIdent) { + StringBuilder builder = new StringBuilder(); + String parameterText = ""; + if (null != methodIdent.getParameters()) { + List parameterList = new ArrayList(); + for (String parameter : (List) methodIdent.getParameters()) { + String[] split = parameter.split("\\."); + parameterList.add(split[split.length - 1]); + } + + parameterText = parameterList.toString(); + parameterText = parameterText.substring(1, parameterText.length() - 1); + } + + builder.append(methodIdent.getMethodName()); + builder.append('('); + builder.append(parameterText); + builder.append(") "); + + return builder.toString(); + } + + /** + * Returns a String out of the {@link MethodIdent} objects which looks like: 'name'('parameter') + * - 'package'.'class'. + * + * @param methodIdent + * The object which contains the information to create the method string. + * @return The created method string. + */ + public static String getMethodString(MethodIdent methodIdent) { + return getStyledMethodString(methodIdent).getString(); + } + + /** + * Returns styled string for invocation affilliation percentage. + * + * @param percentage + * Percentage. + * @param invocationsNumber + * the number of invocation in total + * @return Styled string. + */ + public static StyledString getInvocationAffilliationPercentageString(int percentage, int invocationsNumber) { + StyledString styledString = new StyledString(); + + styledString.append(String.valueOf(percentage), StyledString.QUALIFIER_STYLER); + styledString.append("% (in ", StyledString.QUALIFIER_STYLER); + styledString.append(String.valueOf(invocationsNumber), StyledString.QUALIFIER_STYLER); + styledString.append(" inv)", StyledString.QUALIFIER_STYLER); + return styledString; + } + + /** + * Creates a StyledString containing a warning. + * + * @return a StyledString containing a warning. + */ + public static StyledString getWarningSign() { + return new StyledString(" !", new Styler() { + + @Override + public void applyStyles(TextStyle textStyle) { + textStyle.foreground = JFaceResources.getColorRegistry().get(JFacePreferences.ERROR_COLOR); + textStyle.font = JFaceResources.getFont(TextFormatter.FONT_ERROR_MARKER); + } + }); + } + + /** + * Get the textual representation of objects that will be displayed in the new view. + * + * @param invAwareData + * Invocation aware object to get representation for. + * @param repositoryDefinition + * Repository definition. Needed for the method name retrival. + * @return String. + */ + public static String getInvocationAwareDataTextualRepresentation(InvocationAwareData invAwareData, RepositoryDefinition repositoryDefinition) { + if (invAwareData instanceof SqlStatementData) { + SqlStatementData sqlData = (SqlStatementData) invAwareData; + return "SQL: " + sqlData.getSql(); + } else if (invAwareData instanceof RegExAggregatedHttpTimerData) { + return "transformed URI: " + ((RegExAggregatedHttpTimerData) invAwareData).getTransformedUri(); + } else if (invAwareData instanceof HttpTimerData) { + HttpTimerData timerData = (HttpTimerData) invAwareData; + // Print either URI or Usecase (tagged value) depending on the situation (which is + // filled, that is) + if (!HttpTimerData.UNDEFINED.equals(timerData.getUri())) { + return "URI: " + timerData.getUri(); + } else { + return "Usecase: " + timerData.getInspectItTaggingHeaderValue(); + } + } else if (invAwareData instanceof ExceptionSensorData) { + ExceptionSensorData exData = (ExceptionSensorData) invAwareData; + return "Exception: " + exData.getThrowableType(); + } else if (invAwareData instanceof TimerData) { + TimerData timerData = (TimerData) invAwareData; + MethodIdent methodIdent = repositoryDefinition.getCachedDataService().getMethodIdentForId(timerData.getMethodIdent()); + return TextFormatter.getMethodString(methodIdent); + } + return ""; + } + + /** + * Returns the styled string for the storage data and its CMR repository definition. + * + * @param storageData + * {@link StorageData}. + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + * @return Styled string for nicer representation. + */ + public static StyledString getStyledStorageDataString(StorageData storageData, CmrRepositoryDefinition cmrRepositoryDefinition) { + StyledString styledString = new StyledString(); + styledString.append(storageData.getName()); + styledString.append(" "); + styledString.append("[" + cmrRepositoryDefinition.getName() + "]", StyledString.QUALIFIER_STYLER); + styledString.append(" - "); + styledString.append(getStorageStateTextualRepresentation(storageData.getState()), StyledString.DECORATIONS_STYLER); + if (InspectIT.getDefault().getInspectITStorageManager().isFullyDownloaded(storageData)) { + styledString.append(", Downloaded", StyledString.DECORATIONS_STYLER); + } + styledString.append(", " + NumberFormatter.humanReadableByteCount(storageData.getDiskSize()), StyledString.DECORATIONS_STYLER); + return styledString; + } + + /** + * Returns the styled string for the {@link LocalStorageData}. + * + * @param localStorageData + * Local storage data. + * @return Styled string for nicer representation. + */ + public static StyledString getStyledStorageDataString(LocalStorageData localStorageData) { + StyledString styledString = new StyledString(); + styledString.append(localStorageData.getName()); + styledString.append(" "); + styledString.append("[Local Disk]", StyledString.QUALIFIER_STYLER); + styledString.append(" - "); + styledString.append(NumberFormatter.humanReadableByteCount(localStorageData.getDiskSize()), StyledString.DECORATIONS_STYLER); + return styledString; + } + + /** + * Returns {@link StyledString} for the {@link AgentLeaf}. + * + * @param agentLeaf + * {@link AgentLeaf}. + * @return Returns {@link StyledString} for the {@link AgentLeaf}. + */ + public static StyledString getStyledAgentLeafString(AgentLeaf agentLeaf) { + StyledString styledString = new StyledString(); + if (agentLeaf.isInFolder()) { + styledString.append(AgentFolderFactory.getAgentDisplayNameInFolder(agentLeaf.getPlatformIdent().getAgentName())); + } else { + styledString.append(agentLeaf.getPlatformIdent().getAgentName()); + } + styledString.append(getStyledAgentDescription(agentLeaf.getPlatformIdent(), agentLeaf.getAgentStatusData())); + return styledString; + } + + /** + * Returns the styled information about the agent version and connection status. + * + * @param platformIdent + * {@link PlatformIdent} + * @param agentStatusData + * {@link AgentStatusData} + * @return {@link StyledString} + */ + private static StyledString getStyledAgentDescription(PlatformIdent platformIdent, AgentStatusData agentStatusData) { + StyledString styledString = new StyledString(); + styledString.append(" "); + styledString.append("[" + platformIdent.getVersion() + "]", StyledString.QUALIFIER_STYLER); + styledString.append(" - "); + if (null != agentStatusData) { + switch (agentStatusData.getAgentConnection()) { + case CONNECTED: + if (null != agentStatusData.getMillisSinceLastData()) { + long millis = agentStatusData.getMillisSinceLastData().longValue(); + // at last one minute of not sending data to display as the non active + if (millis > 60000) { + styledString.append("Connected :: Last data sent " + NumberFormatter.humanReadableMillisCount(millis, true) + " ago", StyledString.DECORATIONS_STYLER); + } else { + styledString.append("Connected :: Sending data", StyledString.DECORATIONS_STYLER); + } + } else { + styledString.append("Connected :: No data sent", StyledString.DECORATIONS_STYLER); + } + break; + case DISCONNECTED: + styledString.append("Disconnected", StyledString.DECORATIONS_STYLER); + break; + default: + styledString.append("Not connected", StyledString.DECORATIONS_STYLER); + break; + } + } else { + styledString.append("Not connected", StyledString.DECORATIONS_STYLER); + } + return styledString; + } + + /** + * @param storageState + * Storage state. + * @return Returns the textual representation of the storage state. + */ + public static String getStorageStateTextualRepresentation(StorageState storageState) { + if (storageState == StorageState.CREATED_NOT_OPENED) { + return "Created"; + } else if (storageState == StorageState.OPENED) { + return "Writable"; + } else if (storageState == StorageState.CLOSED) { + return "Readable"; + } else if (storageState == StorageState.RECORDING) { + return "Recording"; + } + return "UNKNOWN STATE"; + } + + /** + * Returns the name of the label, based on it's type. If label is null, string + * "null" will be returned. + * + * @param label + * Label to get name for. + * @return Returns the name of the label, based on it's class. + */ + public static String getLabelName(AbstractStorageLabel label) { + if (null == label) { + return "null"; + } else { + return getLabelName(label.getStorageLabelType()); + } + } + + /** + * Returns the name of the label type. If label type is null, string "null" will be + * returned. + * + * @param labelType + * Label type to get name for. + * @return Returns the name of the label, based on it's class. + */ + public static String getLabelName(AbstractStorageLabelType labelType) { + if (null == labelType) { + return "null"; + } else if (AssigneeLabelType.class.equals(labelType.getClass())) { + return "Assignee"; + } else if (CreationDateLabelType.class.equals(labelType.getClass())) { + return "Creation Date"; + } else if (ExploredByLabelType.class.equals(labelType.getClass())) { + return "Explored By"; + } else if (RatingLabelType.class.equals(labelType.getClass())) { + return "Rating"; + } else if (StatusLabelType.class.equals(labelType.getClass())) { + return "Status"; + } else if (UseCaseLabelType.class.equals(labelType.getClass())) { + return "Use Case"; + } else if (DataTimeFrameLabelType.class.equals(labelType.getClass())) { + return "Data Timeframe"; + } else if (AbstractCustomStorageLabelType.class.isAssignableFrom(labelType.getClass())) { + return ((AbstractCustomStorageLabelType) labelType).getName(); + } else { + return "Unknown Label"; + } + } + + /** + * Returns the class type of the label type. + * + * @param labelType + * Label type to get name for. + * @return Returns the class type of the label type. + */ + public static String getLabelValueType(AbstractStorageLabelType labelType) { + if (null == labelType) { + return "null"; + } else if (Boolean.class.equals(labelType.getValueClass())) { + return "Yes/No"; + } else if (Date.class.equals(labelType.getValueClass())) { + return "Date"; + } else if (Number.class.equals(labelType.getValueClass())) { + return "Number"; + } else if (String.class.equals(labelType.getValueClass())) { + return "Text"; + } else { + return "Unknown Label Type"; + } + } + + /** + * Returns the name of the label, based on it's class. If label is null, string + * "null" will be returned. + * + * @param label + * Label to get name for. + * @param grouped + * Is is a representation for the grouped labels. + * @return Returns the name of the label, based on it's class. + */ + public static String getLabelValue(AbstractStorageLabel label, boolean grouped) { + if (null == label) { + return "null"; + } else if (CreationDateLabelType.class.equals(label.getStorageLabelType().getClass())) { + Date date = (Date) ((AbstractStorageLabel) label).getValue(); + if (grouped) { + return DateFormat.getDateInstance().format(date); + } else { + return DateFormat.getDateTimeInstance().format(date); + } + } else if (BooleanStorageLabel.class.equals(label.getClass())) { + BooleanStorageLabel booleanStorageLabel = (BooleanStorageLabel) label; + if (booleanStorageLabel.getValue().booleanValue()) { + return "Yes"; + } else { + return "No"; + } + } else { + return label.getFormatedValue(); + } + } + + /** + * Returns text representation for the {@link WritingStatus} or empty string if status is + * null. + * + * @param recordingWritingStatus + * Status of writing. + * @return String that represent the status. + */ + public static String getWritingStatusText(WritingStatus recordingWritingStatus) { + if (null == recordingWritingStatus) { + return ""; + } + switch (recordingWritingStatus) { + case GOOD: + return "[OK] There are no problems."; + case MEDIUM: + return "[WARN] Amount of data tried to be recorded is slightly higer than what CMR can support. However, no data loss should be expected."; + case BAD: + return "[ALERT] Amount of data tried to be recorded is too high for CMR to manage. Data loss should be expected."; + default: + return ""; + } + } + + /** + * Description of the agent. + * + * @param agent + * {@link PlatformIdent} + * @return Description of the agent. + */ + public static String getAgentDescription(PlatformIdent agent) { + return agent.getAgentName() + " [" + agent.getVersion() + "]"; + } + + /** + * Description of the agent with the connection information. + * + * @param agent + * {@link PlatformIdent} + * @param agentStatusData + * {@link AgentStatusData}. + * @return Description of the agent. + */ + public static String getAgentDescription(PlatformIdent agent, AgentStatusData agentStatusData) { + return agent.getAgentName() + getStyledAgentDescription(agent, agentStatusData).getString(); + } + + /** + * Returns formated {@link String} for the {@link SqlStatementData} parameter values list. + *

+ * Elements that are null in the list will be printed as '?'. + * + * @param parameterValues + * List of parameter values. + * @return Formated string in form [param1, param2,.., paramN]. + */ + public static String getSqlParametersText(List parameterValues) { + if (null == parameterValues || parameterValues.isEmpty()) { + return "[]"; + } else { + Iterator it = parameterValues.iterator(); + StringBuilder sb = new StringBuilder(); + sb.append('['); + while (it.hasNext()) { + String param = it.next(); + if (null != param) { + sb.append(param); + } else { + sb.append('?'); + } + if (it.hasNext()) { + sb.append(", "); + } + } + return sb.append(']').toString(); + } + } + + /** + * The original text will be cleaned from the line breaks. + *

+ * If string passed is null, null will be returned. + * + * @param originalText + * Original text to modify. + * @return Returns text without any line breaks. + */ + public static String clearLineBreaks(String originalText) { + if (null == originalText) { + return originalText; + } + boolean lastCharWhitespace = false; + StringBuilder stringBuilder = new StringBuilder(originalText.length()); + + for (int i = 0; i < originalText.length(); i++) { + char c = originalText.charAt(i); + if (c == '\r' || c == '\n') { + if (!lastCharWhitespace) { + stringBuilder.append(' '); + lastCharWhitespace = true; + } + } else if (Character.isWhitespace(c)) { + if (!lastCharWhitespace) { + stringBuilder.append(' '); + lastCharWhitespace = true; + } + } else { + stringBuilder.append(c); + lastCharWhitespace = false; + } + } + return stringBuilder.toString(); + } + + /** + * Crops the string to the maxLength. The string will have '...' appended at the end. This + * method delegates to the {@link StringUtils#abbreviate(String, int)} method. + * + * @param string + * String to crop. + * @param maxLength + * Wanted maximum length. + * @see StringUtils#abbreviate(String, int) + * @return Cropped {@link String}. + */ + public static String crop(String string, int maxLength) { + return StringUtils.abbreviate(string, maxLength); + } + + /** + * Returns a new StyledString that contains the given text or "" if the given text was in fact + * null. + * + * @param text + * the text to display, may be null. + * @return a new StyledString that contains the given text or "" if the given text was in fact + * null. + */ + public static StyledString emptyStyledStringIfNull(String text) { + return new StyledString(emptyStringIfNull(text)); + } + + /** + * Returns a String that contains the given text or "" if the given text was in fact + * null. + * + * @param text + * the text to display, may be null. + * @return a new StyledString that contains the given text or "" if the given text was in fact + * null. + */ + public static String emptyStringIfNull(String text) { + if (!StringUtils.isEmpty(text)) { + return text; + } else { + return ""; + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/AbstractTemplateHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/AbstractTemplateHandler.java new file mode 100644 index 000000000..4d381fcb3 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/AbstractTemplateHandler.java @@ -0,0 +1,127 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.util.OccurrenceFinderFactory; + +import org.eclipse.core.commands.AbstractHandler; + +/** + * Handler that know how the template objects are created. All handler that need to create template + * objects should extend this handler. + * + * @author Ivan Senic + * + */ +public abstract class AbstractTemplateHandler extends AbstractHandler { + + /** + * Returns template for {@link SqlStatementData}. + * + * @param sqlStatementData + * Source object. + * @param id + * Should id be inserted into template. + * @param sql + * Should SQL query be inserted into template. + * @param parameters + * Should parameters be inserted into template. + * @return Template object. + */ + protected SqlStatementData getTemplate(SqlStatementData sqlStatementData, boolean id, boolean sql, boolean parameters) { + SqlStatementData template = OccurrenceFinderFactory.getEmptyTemplate(sqlStatementData); + if (id && 0 != sqlStatementData.getId()) { + template.setId(sqlStatementData.getId()); + } + if (sql && null != sqlStatementData.getSql()) { + template.setSql(sqlStatementData.getSql()); + } + if (parameters && null != sqlStatementData.getParameterValues()) { + template.setParameterValues(sqlStatementData.getParameterValues()); + } + return template; + } + + /** + * Returns template for {@link ExceptionSensorData}. + * + * @param exceptionSensorData + * Source object. + * @param id + * Should id be inserted into template. + * @param throwableType + * Should throwable type be inserted into template. + * @param exceptionEvent + * Should exception event be inserted into template. + * @param errorMessage + * Should error message be inserted into template. + * @param stackTrace + * Should stack trace be inserted into template. + * @return Template object. + */ + protected ExceptionSensorData getTemplate(ExceptionSensorData exceptionSensorData, boolean id, boolean throwableType, boolean exceptionEvent, boolean errorMessage, boolean stackTrace) { + ExceptionSensorData template = OccurrenceFinderFactory.getEmptyTemplate(exceptionSensorData); + if (id && 0 != exceptionSensorData.getId()) { + template.setId(exceptionSensorData.getId()); + } + if (throwableType && null != exceptionSensorData.getThrowableType()) { + template.setThrowableType(exceptionSensorData.getThrowableType()); + } + if (exceptionEvent && null != exceptionSensorData.getExceptionEvent()) { + template.setExceptionEvent(exceptionSensorData.getExceptionEvent()); + } + if (errorMessage && null != exceptionSensorData.getErrorMessage()) { + template.setErrorMessage(exceptionSensorData.getErrorMessage()); + } + if (stackTrace && null != exceptionSensorData.getStackTrace()) { + template.setStackTrace(exceptionSensorData.getStackTrace()); + } + return template; + } + + /** + * Returns template for {@link TimerData}. + * + * @param timerData + * Source object. + * @param id + * Should id be inserted into template. + * @param methodIdent + * Should methodIdent be inserted into template. + * @return Template object. + */ + protected TimerData getTemplate(TimerData timerData, boolean id, boolean methodIdent) { + TimerData template = OccurrenceFinderFactory.getEmptyTemplate(timerData); + if (id && 0 != timerData.getId()) { + template.setId(timerData.getId()); + } + if (methodIdent && 0 != timerData.getMethodIdent()) { + template.setMethodIdent(timerData.getMethodIdent()); + } + return template; + } + + /** + * Returns template for {@link InvocationSequenceData}. + * + * @param invocationSequenceData + * Source object. + * @param id + * Should id be inserted into template. + * @param methodIdent + * Should methodIdent be inserted into template. + * @return Template object. + */ + protected InvocationSequenceData getTemplate(InvocationSequenceData invocationSequenceData, boolean id, boolean methodIdent) { + InvocationSequenceData template = OccurrenceFinderFactory.getEmptyTemplate(invocationSequenceData); + if (id && 0 != invocationSequenceData.getId()) { + template.setId(invocationSequenceData.getId()); + } + if (methodIdent && 0 != invocationSequenceData.getMethodIdent()) { + template.setMethodIdent(invocationSequenceData.getMethodIdent()); + } + return template; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/AddStorageLabelHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/AddStorageLabelHandler.java new file mode 100644 index 000000000..8252166da --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/AddStorageLabelHandler.java @@ -0,0 +1,61 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.provider.IStorageDataProvider; +import info.novatec.inspectit.rcp.view.impl.StorageManagerView; +import info.novatec.inspectit.rcp.wizard.AddStorageLabelWizard; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Handler for adding a label to storage. + * + * @author Ivan Senic + * + */ +public class AddStorageLabelHandler extends AbstractHandler implements IHandler { + + /** + * The corresponding command id. + */ + public static final String COMMAND = "info.novatec.inspectit.rcp.commands.addStorageLabel"; + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IStorageDataProvider storageProvider = null; + + // try to get it from selection + ISelection selection = HandlerUtil.getCurrentSelection(event); + if (selection instanceof StructuredSelection) { + if (((StructuredSelection) selection).getFirstElement() instanceof IStorageDataProvider) { + storageProvider = (IStorageDataProvider) ((StructuredSelection) selection).getFirstElement(); + } + } + + if (null != storageProvider) { + AddStorageLabelWizard addStorageLabelWizard = new AddStorageLabelWizard(storageProvider); + WizardDialog wizardDialog = new WizardDialog(HandlerUtil.getActiveShell(event), addStorageLabelWizard); + wizardDialog.open(); + if (wizardDialog.getReturnCode() == WizardDialog.OK) { + IViewPart viewPart = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().findView(StorageManagerView.VIEW_ID); + if (viewPart instanceof StorageManagerView) { + ((StorageManagerView) viewPart).refresh(storageProvider.getCmrRepositoryDefinition()); + } + } + } + + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/ClearRepositoryBufferHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/ClearRepositoryBufferHandler.java new file mode 100644 index 000000000..4ef2444f8 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/ClearRepositoryBufferHandler.java @@ -0,0 +1,114 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.preferences.PreferenceId; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; +import info.novatec.inspectit.rcp.provider.ICmrRepositoryProvider; +import info.novatec.inspectit.rcp.provider.IInputDefinitionProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.view.impl.RepositoryManagerView; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.Collections; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IEditorReference; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.progress.IProgressConstants; + +/** + * Handler for clearing the repository buffer. + * + * @author Ivan Senic + * + */ +public class ClearRepositoryBufferHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(final ExecutionEvent event) throws ExecutionException { + CmrRepositoryDefinition availableCmr = null; + + ISelection selection = HandlerUtil.getCurrentSelection(event); + if (selection instanceof StructuredSelection) { + Object selectedObject = ((StructuredSelection) selection).getFirstElement(); + if (selectedObject instanceof ICmrRepositoryProvider) { + ICmrRepositoryProvider cmrRepositoryProvider = (ICmrRepositoryProvider) selectedObject; + availableCmr = cmrRepositoryProvider.getCmrRepositoryDefinition(); + } + } + if (null == availableCmr) { + IWorkbenchPart editor = HandlerUtil.getActivePart(event); + if (editor instanceof IInputDefinitionProvider) { + IInputDefinitionProvider inputDefinitionProvider = (IInputDefinitionProvider) editor; + if (inputDefinitionProvider.getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) { + availableCmr = (CmrRepositoryDefinition) inputDefinitionProvider.getInputDefinition().getRepositoryDefinition(); + } + } + } + + final CmrRepositoryDefinition cmrRepositoryDefinition = availableCmr; + if (null != cmrRepositoryDefinition && cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + boolean isSure = MessageDialog.openConfirm(null, "Empty buffer", + "Are you sure that you want to completely delete all the data in the buffer on repository " + cmrRepositoryDefinition.getName() + " (" + cmrRepositoryDefinition.getIp() + ":" + + cmrRepositoryDefinition.getPort() + ")?"); + if (isSure) { + Job clearBufferJob = new Job("Clear Respoitory Buffer") { + @Override + protected IStatus run(IProgressMonitor monitor) { + cmrRepositoryDefinition.getCmrManagementService().clearBuffer(); + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindow(event); + IEditorReference[] editors = window.getActivePage().getEditorReferences(); + for (IEditorReference editor : editors) { + IRootEditor rootEditor = (IRootEditor) editor.getEditor(false); + if (null != rootEditor.getPreferencePanel()) { + if (rootEditor.getSubView().getPreferenceIds().contains(PreferenceId.CLEAR_BUFFER)) { + InputDefinition inputDefinition = rootEditor.getInputDefinition(); + if (ObjectUtils.equals(inputDefinition.getRepositoryDefinition(), cmrRepositoryDefinition)) { + rootEditor.getSubView().setDataInput(Collections. emptyList()); + } + } + } + } + IViewPart viewPart = window.getActivePage().findView(RepositoryManagerView.VIEW_ID); + if (viewPart instanceof RepositoryManagerView) { + ((RepositoryManagerView) viewPart).refresh(); + } + + } + }); + return Status.OK_STATUS; + } + }; + clearBufferJob.setUser(true); + clearBufferJob.setProperty(IProgressConstants.ICON_PROPERTY, InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_BUFFER_CLEAR)); + clearBufferJob.schedule(); + } + } + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/CloseAndShowStorageHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/CloseAndShowStorageHandler.java new file mode 100644 index 000000000..780d3a47f --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/CloseAndShowStorageHandler.java @@ -0,0 +1,132 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.provider.IStorageDataProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; +import info.novatec.inspectit.rcp.storage.InspectITStorageManager; +import info.novatec.inspectit.rcp.view.impl.DataExplorerView; +import info.novatec.inspectit.rcp.view.impl.StorageManagerView; +import info.novatec.inspectit.storage.StorageData; + +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * This handler starts the storage finalization job, and when it is done opens the finalized storage + * in the {@link DataExplorerView}. + * + * @author Ivan Senic + * + */ +public class CloseAndShowStorageHandler extends CloseStorageHandler implements IHandler { + + /** + * Command id. + */ + public static final String COMMAND = "info.novatec.inspectit.rcp.commands.closeAndShowStorage"; + + /** + * Parameter id. + */ + public static final String STORAGE_DATA_PROVIDER = "info.novatec.inspectit.rcp.commands.closeAndShowStorage.param"; + + /** + * {@inheritDoc} + */ + @Override + public Object execute(final ExecutionEvent event) throws ExecutionException { + IStorageDataProvider storageDataProvider = (IStorageDataProvider) HandlerUtil.getVariable(event, STORAGE_DATA_PROVIDER); + + final StorageData storageData = storageDataProvider.getStorageData(); + final CmrRepositoryDefinition cmrRepositoryDefinition = storageDataProvider.getCmrRepositoryDefinition(); + + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + FinalizeStorageJob finalizeStorageJob = new FinalizeStorageJob(storageData, cmrRepositoryDefinition); + finalizeStorageJob.schedule(); + finalizeStorageJob.addJobChangeListener(new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent jobEvent) { + Job mountStorageJob = new Job("Mounting Storage") { + @Override + protected IStatus run(IProgressMonitor monitor) { + SubMonitor subMonitor = SubMonitor.convert(monitor); + InspectITStorageManager storageManager = InspectIT.getDefault().getInspectITStorageManager(); + try { + storageManager.mountStorage(storageData, cmrRepositoryDefinition, subMonitor); + } catch (final Exception exception) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + InspectIT.getDefault().createErrorDialog("There was an exception trying to open the storage.", exception, -1); + } + }); + } + RepositoryDefinition repositoryDefinition = null; + try { + repositoryDefinition = storageManager.getStorageRepositoryDefinition(storageManager.getLocalDataForStorage(storageData)); + } catch (final Exception exception) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + InspectIT.getDefault().createErrorDialog("There was an exception trying to open the storage.", exception, -1); + } + }); + } + + // find views + final IWorkbenchPage page = HandlerUtil.getActiveSite(event).getPage(); + final RepositoryDefinition finalRepositoryDefinition = repositoryDefinition; + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + IViewPart dataExplorerView = page.findView(DataExplorerView.VIEW_ID); + IViewPart storageManagerView = page.findView(StorageManagerView.VIEW_ID); + if (dataExplorerView == null) { + try { + dataExplorerView = page.showView(DataExplorerView.VIEW_ID); + } catch (PartInitException e) { + return; + } + } + if (dataExplorerView instanceof DataExplorerView) { + PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().activate(dataExplorerView); + ((DataExplorerView) dataExplorerView).showRepository(finalRepositoryDefinition, null); + } + + if (storageManagerView instanceof StorageManagerView) { + ((StorageManagerView) storageManagerView).refresh(cmrRepositoryDefinition); + } + } + }); + monitor.done(); + return Status.OK_STATUS; + + } + }; + mountStorageJob.setUser(true); + mountStorageJob.schedule(); + } + }); + + } else { + InspectIT.getDefault().createInfoDialog("Can not finalize storage, CMR repository is offline.", -1); + } + return null; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/CloseStorageHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/CloseStorageHandler.java new file mode 100644 index 000000000..65a3e1117 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/CloseStorageHandler.java @@ -0,0 +1,179 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.provider.IStorageDataProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.view.impl.StorageManagerView; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageException; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.MessageBox; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.progress.IProgressConstants; + +/** + * Tries to close the list of storages given through the storage leaf. + * + * @author Ivan Senic + * + */ +public class CloseStorageHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(final ExecutionEvent event) throws ExecutionException { + final ISelection selection = HandlerUtil.getCurrentSelection(event); + if (selection instanceof StructuredSelection) { + Object firstElement = ((StructuredSelection) selection).getFirstElement(); + if (firstElement instanceof IStorageDataProvider) { + StorageData storageData = ((IStorageDataProvider) firstElement).getStorageData(); + CmrRepositoryDefinition cmrRepositoryDefinition = ((IStorageDataProvider) firstElement).getCmrRepositoryDefinition(); + + MessageBox confirmFinalization = new MessageBox(HandlerUtil.getActiveShell(event), SWT.OK | SWT.CANCEL | SWT.ICON_QUESTION); + confirmFinalization.setText("Confirm Finalization"); + confirmFinalization + .setMessage("Are you sure you want to finalize the selected storage? Writing will not be possible after finalization. Note that finalization process will wait for all ongoing writing tasks to be finished."); + + if (SWT.OK == confirmFinalization.open()) { + FinalizeStorageJob finalizeStorageJob = new FinalizeStorageJob(storageData, cmrRepositoryDefinition); + finalizeStorageJob.schedule(); + } + } + } + return null; + } + + /** + * Finalize storage job class. Starts the finalization and provides information about the amount + * of tasks left before finalization can be done. + * + * @author Ivan Senic + * + */ + protected static class FinalizeStorageJob extends Job { + + /** + * Amount of milliseconds job will check for the amount of writing tasks left. + */ + private static final long TASKS_CHECK_SLEEP_TIME = 1000; + + /** + * Storage to finalize. + */ + private StorageData storageData; + + /** + * CMR where storage is located. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * + * @param storageData + * Storage to finalize. + * @param cmrRepositoryDefinition + * CMR where storage is located. + */ + public FinalizeStorageJob(StorageData storageData, CmrRepositoryDefinition cmrRepositoryDefinition) { + super("Finalizing storage " + storageData); + this.storageData = storageData; + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + setUser(true); + setProperty(IProgressConstants.ICON_PROPERTY, InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_STORAGE_FINALIZE)); + } + + /** + * {@inheritDoc} + */ + @Override + protected IStatus run(IProgressMonitor monitor) { + // cancel if CMR is not online + if (cmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.OFFLINE) { + return Status.CANCEL_STATUS; + } + + // get the number of tasks + int totalTasks = (int) cmrRepositoryDefinition.getStorageService().getStorageQueuedWriteTaskCount(storageData); + + String taskName; + if (totalTasks > 0) { + taskName = "Waiting for " + totalTasks + " writing tasks to finish and finalizing storage '" + storageData.getName() + "'"; + } else { + taskName = "Finalizing storage '" + storageData + "'"; + } + + monitor.beginTask(taskName, totalTasks + 1); + + Job executeFinalization = new Job("Execute finalization") { + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + cmrRepositoryDefinition.getStorageService().closeStorage(storageData); + } catch (final StorageException e) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + InspectIT.getDefault().createErrorDialog("Selected storage " + storageData + " could not be finalized.", e, -1); + } + }); + } + return Status.OK_STATUS; + } + }; + executeFinalization.setUser(false); + executeFinalization.schedule(); + + // regulate the processed task count + while (executeFinalization.getState() != Job.NONE) { + try { + Thread.sleep(TASKS_CHECK_SLEEP_TIME); + } catch (InterruptedException e) { + Thread.interrupted(); + } + if (executeFinalization.getState() == Job.NONE) { + monitor.worked(totalTasks); + } else { + int newLeftTasks = (int) cmrRepositoryDefinition.getStorageService().getStorageQueuedWriteTaskCount(storageData); + monitor.worked(totalTasks - newLeftTasks); + totalTasks = newLeftTasks; + } + } + + // add one task for finalization + monitor.worked(1); + monitor.done(); + + // refresh the storage manager + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + IViewPart viewPart = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().findView(StorageManagerView.VIEW_ID); + if (viewPart instanceof StorageManagerView) { + ((StorageManagerView) viewPart).refresh(cmrRepositoryDefinition); + } + } + }); + + return Status.OK_STATUS; + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/CmrConfigurationHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/CmrConfigurationHandler.java new file mode 100644 index 000000000..9b76910b3 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/CmrConfigurationHandler.java @@ -0,0 +1,120 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.cmr.property.update.configuration.ConfigurationUpdate; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.property.CmrConfigurationDialog; +import info.novatec.inspectit.rcp.provider.ICmrRepositoryProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Handler for starting the CMR configuration. + * + * @author Ivan Senic + * + */ +public class CmrConfigurationHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + StructuredSelection selection = (StructuredSelection) HandlerUtil.getCurrentSelection(event); + if (selection.getFirstElement() instanceof ICmrRepositoryProvider) { + Shell shell = HandlerUtil.getActiveShell(event); + CmrRepositoryDefinition cmrRepositoryDefinition = ((ICmrRepositoryProvider) selection.getFirstElement()).getCmrRepositoryDefinition(); + CmrConfigurationDialog preferenceDialog = new CmrConfigurationDialog(shell, cmrRepositoryDefinition); + preferenceDialog.open(); + if (Dialog.OK == preferenceDialog.getReturnCode()) { + ConfigurationUpdate configurationUpdate = preferenceDialog.getConfigurationUpdate(); + boolean restartRequired = preferenceDialog.isServerRestartRequired(); + if (null != configurationUpdate) { + boolean executeRestart = false; + if (restartRequired) { + String msg = "Selected updates need server restart to be effective. Do you want to restart the CMR?"; + executeRestart = MessageDialog.openQuestion(shell, "CMR Restart Required", msg); + } + new ConfigurationUpdateJob(cmrRepositoryDefinition, configurationUpdate, executeRestart).schedule(); + } + } + } + return null; + } + + /** + * Job for updating the configuration. + * + * @author Ivan Senic + * + */ + private static final class ConfigurationUpdateJob extends Job { + + /** + * CMR to update. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * {@link ConfigurationUpdate}. + */ + private ConfigurationUpdate configurationUpdate; + + /** + * If user has selected that restart should be automatically executed. + */ + private boolean executeRestart; + + /** + * Default constructor. + * + * @param cmrRepositoryDefinition + * CMR to update. + * @param configurationUpdate + * {@link ConfigurationUpdate} + * @param executeRestart + * If user has selected that restart should be automatically executed. + */ + public ConfigurationUpdateJob(CmrRepositoryDefinition cmrRepositoryDefinition, ConfigurationUpdate configurationUpdate, boolean executeRestart) { + super("Update CMR Configuration Job"); + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + this.configurationUpdate = configurationUpdate; + this.executeRestart = executeRestart; + setUser(true); + } + + /** + * {@inheritDoc} + */ + @Override + protected IStatus run(IProgressMonitor monitor) { + monitor.beginTask("Updating the CMR configuration", IProgressMonitor.UNKNOWN); + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + try { + cmrRepositoryDefinition.getCmrManagementService().updateConfiguration(configurationUpdate, executeRestart); + } catch (Exception e) { + return new Status(Status.ERROR, InspectIT.ID, "Exception occurred trying to update the CMR configuration.", e); + } + monitor.done(); + return Status.OK_STATUS; + } else { + return new Status(Status.ERROR, InspectIT.ID, "Can not update the configuration because selected CMR is offline."); + } + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/CopyBufferToStorageHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/CopyBufferToStorageHandler.java new file mode 100644 index 000000000..a3db3ec17 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/CopyBufferToStorageHandler.java @@ -0,0 +1,73 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.communication.data.cmr.CmrStatusData; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.provider.ICmrRepositoryAndAgentProvider; +import info.novatec.inspectit.rcp.provider.ICmrRepositoryProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.wizard.CopyBufferToStorageWizard; + +import java.util.Collection; +import java.util.Collections; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Copy buffer to storage handler. + * + * @author Ivan Senic + * + */ +public class CopyBufferToStorageHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + ISelection selection = HandlerUtil.getCurrentSelection(event); + if (selection instanceof StructuredSelection) { + CmrRepositoryDefinition suggestedCmrRepositoryDefinition = null; + Collection autoSelectedAgents = Collections.emptyList(); + Object selectedObject = ((StructuredSelection) selection).getFirstElement(); + if (selectedObject instanceof ICmrRepositoryProvider) { + suggestedCmrRepositoryDefinition = ((ICmrRepositoryProvider) selectedObject).getCmrRepositoryDefinition(); + } else if (selectedObject instanceof ICmrRepositoryAndAgentProvider) { + suggestedCmrRepositoryDefinition = ((ICmrRepositoryAndAgentProvider) selectedObject).getCmrRepositoryDefinition(); + autoSelectedAgents = Collections.singletonList(((ICmrRepositoryAndAgentProvider) selectedObject).getPlatformIdent()); + } + if (null != suggestedCmrRepositoryDefinition) { + // check if the writing state is OK + try { + CmrStatusData cmrStatusData = suggestedCmrRepositoryDefinition.getCmrManagementService().getCmrStatusData(); + if (cmrStatusData.isWarnSpaceLeftActive()) { + String leftSpace = NumberFormatter.humanReadableByteCount(cmrStatusData.getStorageDataSpaceLeft()); + if (!MessageDialog.openQuestion(HandlerUtil.getActiveShell(event), "Confirm", "For selected CMR there is an active warning about insufficient storage space left. Only " + + leftSpace + " are left on the target server, are you sure you want to continue?")) { + return null; + } + } + } catch (Exception e) { // NOPMD NOCHK + // ignore because if we can not get the info. we will still respond to user + // action + } + + CopyBufferToStorageWizard wizard = new CopyBufferToStorageWizard(suggestedCmrRepositoryDefinition, autoSelectedAgents); + WizardDialog dialog = new WizardDialog(HandlerUtil.getActiveShell(event), wizard); + dialog.open(); + } + } + + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/CopyDataToStorageHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/CopyDataToStorageHandler.java new file mode 100644 index 000000000..c20fcfb94 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/CopyDataToStorageHandler.java @@ -0,0 +1,83 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.cmr.CmrStatusData; +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; +import info.novatec.inspectit.rcp.wizard.CopyDataToStorageWizard; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Handler for copying data to storage. + * + * @author Ivan Senic + * + */ +public class CopyDataToStorageHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + AbstractRootEditor rootEditor = (AbstractRootEditor) HandlerUtil.getActiveEditor(event); + RepositoryDefinition repositoryDefinition = rootEditor.getInputDefinition().getRepositoryDefinition(); + StructuredSelection selection = (StructuredSelection) HandlerUtil.getCurrentSelection(event); + + Set copyDataSet = new HashSet(selection.size()); + for (Iterator it = selection.iterator(); it.hasNext();) { + Object nextObject = it.next(); + + if (nextObject instanceof InvocationSequenceData) { + InvocationSequenceData invocationSequenceData = (InvocationSequenceData) nextObject; + while (null != invocationSequenceData.getParentSequence()) { + invocationSequenceData = invocationSequenceData.getParentSequence(); + } + copyDataSet.add(invocationSequenceData); + } else if (nextObject instanceof DefaultData) { + copyDataSet.add((DefaultData) nextObject); + } + } + + if (!copyDataSet.isEmpty() && repositoryDefinition instanceof CmrRepositoryDefinition) { + CmrRepositoryDefinition cmrRepositoryDefinition = (CmrRepositoryDefinition) repositoryDefinition; + + // check if the writing state is OK + try { + CmrStatusData cmrStatusData = cmrRepositoryDefinition.getCmrManagementService().getCmrStatusData(); + if (cmrStatusData.isWarnSpaceLeftActive()) { + String leftSpace = NumberFormatter.humanReadableByteCount(cmrStatusData.getStorageDataSpaceLeft()); + if (!MessageDialog.openQuestion(HandlerUtil.getActiveShell(event), "Confirm", "For selected CMR there is an active warning about insufficient storage space left. Only " + + leftSpace + " are left on the target server, are you sure you want to continue?")) { + return null; + } + } + } catch (Exception e) { // NOPMD NOCHK + // ignore because if we can not get the info. we will still respond to user + // action + } + + CopyDataToStorageWizard wizard = new CopyDataToStorageWizard(cmrRepositoryDefinition, copyDataSet); + WizardDialog dialog = new WizardDialog(HandlerUtil.getActiveShell(event), wizard); + dialog.open(); + } + + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/CopySqlQueryHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/CopySqlQueryHandler.java new file mode 100644 index 000000000..38dab3f37 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/CopySqlQueryHandler.java @@ -0,0 +1,38 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.communication.data.SqlStatementData; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Handler that copies the SQL Query string to the clipboard. + * + * @author Ivan Senic + * + */ +public class CopySqlQueryHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + Object firstElement = ((StructuredSelection) HandlerUtil.getCurrentSelection(event)).getFirstElement(); + if (firstElement instanceof SqlStatementData) { + SqlStatementData sqlStatementData = (SqlStatementData) firstElement; + TextTransfer textTransfer = TextTransfer.getInstance(); + Clipboard cb = new Clipboard(HandlerUtil.getActiveShell(event).getDisplay()); + cb.setContents(new Object[] { sqlStatementData.getSqlWithParameterValues() }, new Transfer[] { textTransfer }); + } + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/DeleteAgentHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/DeleteAgentHandler.java new file mode 100644 index 000000000..b8aa5e338 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/DeleteAgentHandler.java @@ -0,0 +1,55 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.cmr.service.exception.ServiceException; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.model.AgentLeaf; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.util.Iterator; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Handler for deleting the agent from the CMR. + * + * @author Ivan Senic + * + */ +public class DeleteAgentHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelection(event); + boolean confirmed = MessageDialog.openConfirm(HandlerUtil.getActiveShell(event), "Confirm Delete", + "Are you sure you want to permanently delete the selected Agent(s)? Note that all monitoring data related to the Agent(s) will be deleted from the repository database."); + if (confirmed) { + for (Iterator it = selection.iterator(); it.hasNext();) { + Object selected = (Object) it.next(); + if (selected instanceof AgentLeaf) { + AgentLeaf agentLeaf = (AgentLeaf) selected; + PlatformIdent platformIdent = agentLeaf.getPlatformIdent(); + CmrRepositoryDefinition cmrRepositoryDefinition = agentLeaf.getCmrRepositoryDefinition(); + + try { + cmrRepositoryDefinition.getGlobalDataAccessService().deleteAgent(platformIdent.getId()); + InspectIT.getDefault().getCmrRepositoryManager().repositoryAgentDeleted(cmrRepositoryDefinition, platformIdent); + } catch (ServiceException e) { + InspectIT.getDefault().createErrorDialog("Exception occurred trying to delete the Agent from the CMR.", e, -1); + } + } + } + + } + return null; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/DeleteLocalStorageHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/DeleteLocalStorageHandler.java new file mode 100644 index 000000000..d6b611e34 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/DeleteLocalStorageHandler.java @@ -0,0 +1,71 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.provider.ILocalStorageDataProvider; +import info.novatec.inspectit.storage.LocalStorageData; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.MessageBox; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Handler for deleting the local storage. + * + * @author Ivan Senic + * + */ +public class DeleteLocalStorageHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + StructuredSelection structuredSelection = (StructuredSelection) HandlerUtil.getCurrentSelection(event); + List localStoragesToDelete = new ArrayList(); + for (Iterator it = structuredSelection.iterator(); it.hasNext();) { + LocalStorageData localStorageData = ((ILocalStorageDataProvider) it.next()).getLocalStorageData(); + if (localStorageData.isFullyDownloaded()) { + localStoragesToDelete.add(localStorageData); + } + } + + if (!localStoragesToDelete.isEmpty()) { + StringBuffer confirmText = new StringBuffer(100); + boolean plural = localStoragesToDelete.size() > 1; + if (!plural) { + confirmText.append("Are you sure you want to delete the locally downloaded data for the selected storage? "); + } else { + confirmText.append("Are you sure you want to delete the locally downloaded data for the " + localStoragesToDelete.size() + " selected storages? "); + } + + MessageBox confirmDelete = new MessageBox(HandlerUtil.getActiveShell(event), SWT.OK | SWT.CANCEL | SWT.ICON_QUESTION); + confirmDelete.setText("Confirm Delete"); + confirmDelete.setMessage(confirmText.toString()); + + if (SWT.OK == confirmDelete.open()) { + for (LocalStorageData localStorageData : localStoragesToDelete) { + try { + InspectIT.getDefault().getInspectITStorageManager().deleteLocalStorageData(localStorageData); + } catch (Exception e) { + InspectIT.getDefault().createErrorDialog("There was an exception trying to delete local storage data.", e, -1); + return null; + } + } + + } + } + + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/DeleteStorageHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/DeleteStorageHandler.java new file mode 100644 index 000000000..f00f9b2f2 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/DeleteStorageHandler.java @@ -0,0 +1,139 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.provider.IStorageDataProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.label.type.impl.ExploredByLabelType; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.MessageBox; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.progress.IProgressConstants; + +/** + * Handler for deleting a Storage. Handler will inform the user about the users who have mounted the + * storage that is about to be deleted. + * + * @author Ivan Senic + * + */ +public class DeleteStorageHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + ISelection selection = HandlerUtil.getCurrentSelection(event); + if (selection instanceof StructuredSelection) { + Iterator it = ((StructuredSelection) selection).iterator(); + final List storagesToDelete = new ArrayList(); + Set> exploredBySet = new HashSet>(); + boolean confirmed = false; + while (it.hasNext()) { + Object nextObject = it.next(); + if (nextObject instanceof IStorageDataProvider) { + storagesToDelete.add((IStorageDataProvider) nextObject); + exploredBySet.addAll(((IStorageDataProvider) nextObject).getStorageData().getLabels(new ExploredByLabelType())); + } + } + + if (!storagesToDelete.isEmpty()) { + StringBuffer confirmText = new StringBuffer(90); + final boolean plural = storagesToDelete.size() > 1; + if (!plural) { + confirmText.append("Are you sure you want to delete the selected storage? "); + if (!exploredBySet.isEmpty()) { + confirmText.append("Note that the storage was (and still could be) explored by following users: "); + } + } else { + confirmText.append("Are you sure you want to delete the " + storagesToDelete.size() + " selected storages? "); + if (!exploredBySet.isEmpty()) { + confirmText.append("Note that the storages were (and still could be) explored by following users: "); + } + } + if (!exploredBySet.isEmpty()) { + for (AbstractStorageLabel exploredByLabel : exploredBySet) { + confirmText.append("\n * "); + confirmText.append(exploredByLabel.getValue()); + } + } + + MessageBox confirmDelete = new MessageBox(HandlerUtil.getActiveShell(event), SWT.OK | SWT.CANCEL | SWT.ICON_QUESTION); + confirmDelete.setText("Confirm Delete"); + confirmDelete.setMessage(confirmText.toString()); + confirmed = SWT.OK == confirmDelete.open(); + + if (confirmed) { + Job deleteStorageJob = new Job("Delete Storage Job") { + + @Override + protected IStatus run(IProgressMonitor monitor) { + final Set involvedCmrSet = new HashSet(); + for (final IStorageDataProvider storageDataProvider : storagesToDelete) { + if (storageDataProvider.getCmrRepositoryDefinition().getOnlineStatus() != OnlineStatus.OFFLINE) { + involvedCmrSet.add(storageDataProvider.getCmrRepositoryDefinition()); + try { + storageDataProvider.getCmrRepositoryDefinition().getStorageService().deleteStorage(storageDataProvider.getStorageData()); + InspectIT.getDefault().getInspectITStorageManager().storageRemotelyDeleted(storageDataProvider.getStorageData()); + } catch (final StorageException e) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + String name = storageDataProvider.getStorageData().getName(); + InspectIT.getDefault().createErrorDialog("Storage '" + name + "' could not be successfully deleted from CMR.", e, -1); + } + }); + } catch (final Exception e) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + String name = storageDataProvider.getStorageData().getName(); + InspectIT.getDefault().createErrorDialog("Local data for storage '" + name + "' was not cleared successfully.", e, -1); + } + }); + } + } else { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + String name = storageDataProvider.getStorageData().getName(); + InspectIT.getDefault().createInfoDialog("Storage '" + name + "' can not be deleted, because CMR where it is located is offline.", -1); + } + }); + } + } + return Status.OK_STATUS; + } + }; + deleteStorageJob.setUser(true); + deleteStorageJob.setProperty(IProgressConstants.ICON_PROPERTY, InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_DELETE)); + deleteStorageJob.schedule(); + } + } + } + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/DetailsHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/DetailsHandler.java new file mode 100644 index 000000000..b7ab19eea --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/DetailsHandler.java @@ -0,0 +1,68 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.dialog.DetailsDialog; +import info.novatec.inspectit.rcp.provider.IInputDefinitionProvider; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Handler for displaying new details window. + * + * @author Ivan Senic + * + */ +public class DetailsHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(final ExecutionEvent event) throws ExecutionException { + Shell shell = HandlerUtil.getActiveShell(event); + + StructuredSelection selection = (StructuredSelection) HandlerUtil.getCurrentSelection(event); + Object selected = selection.getFirstElement(); + + RepositoryDefinition repositoryDefinition = null; + IWorkbenchPart editor = HandlerUtil.getActivePart(event); + if (editor instanceof IInputDefinitionProvider) { + IInputDefinitionProvider inputDefinitionProvider = (IInputDefinitionProvider) editor; + repositoryDefinition = inputDefinitionProvider.getInputDefinition().getRepositoryDefinition(); + } + + if (selected instanceof DefaultData && null != repositoryDefinition) { + DetailsDialog detailsDialog = new DetailsDialog(shell, (DefaultData) selected, repositoryDefinition); + detailsDialog.open(); + + final Command commandOnClose = detailsDialog.getCommandOnClose(); + if (null != commandOnClose) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + try { + commandOnClose.executeWithChecks(event); + } catch (Exception e) { // NOPMD NOCHK + InspectIT.getDefault().createErrorDialog("Error occurred executing the command in the details dialog.", e, -1); + } + } + }); + + } + } + + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/DownloadStorageHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/DownloadStorageHandler.java new file mode 100644 index 000000000..003eee6d8 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/DownloadStorageHandler.java @@ -0,0 +1,48 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.provider.IStorageDataProvider; +import info.novatec.inspectit.rcp.wizard.DownloadStorageWizard; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Handler for downloading the complete storage. + * + * @author Ivan Senic + * + */ +public class DownloadStorageHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + ISelection selection = HandlerUtil.getCurrentSelection(event); + if (selection instanceof StructuredSelection) { + List storageDataProviders = new ArrayList<>(); + for (Iterator it = ((StructuredSelection) selection).iterator(); it.hasNext();) { + IStorageDataProvider storageDataProvider = (IStorageDataProvider) it.next(); + storageDataProviders.add(storageDataProvider); + } + + if (CollectionUtils.isNotEmpty(storageDataProviders)) { + WizardDialog wizardDialog = new WizardDialog(HandlerUtil.getActiveShell(event), new DownloadStorageWizard(storageDataProviders)); + wizardDialog.open(); + } + } + return null; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/EditCmrRepositoryHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/EditCmrRepositoryHandler.java new file mode 100644 index 000000000..fc65d7081 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/EditCmrRepositoryHandler.java @@ -0,0 +1,51 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.provider.ICmrRepositoryProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.view.impl.RepositoryManagerView; +import info.novatec.inspectit.rcp.wizard.EditCmrRepositoryWizard; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Rename the CMR name and description handler. + * + * @author Ivan Senic + * + */ +public class EditCmrRepositoryHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + CmrRepositoryDefinition cmrRepositoryDefinition = null; + Object selectedElement = ((StructuredSelection) HandlerUtil.getCurrentSelection(event)).getFirstElement(); + if (selectedElement instanceof ICmrRepositoryProvider) { + cmrRepositoryDefinition = ((ICmrRepositoryProvider) selectedElement).getCmrRepositoryDefinition(); + } else { + return null; + } + + EditCmrRepositoryWizard editWizard = new EditCmrRepositoryWizard(cmrRepositoryDefinition); + WizardDialog wizardDialog = new WizardDialog(HandlerUtil.getActiveShell(event), editWizard); + if (WizardDialog.OK == wizardDialog.open()) { + // update view if we have OK from the wizard + IViewPart viewPart = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().findView(RepositoryManagerView.VIEW_ID); + if (viewPart instanceof RepositoryManagerView) { + ((RepositoryManagerView) viewPart).refresh(); + } + } + + return null; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/EditStorageDataHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/EditStorageDataHandler.java new file mode 100644 index 000000000..4880385fa --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/EditStorageDataHandler.java @@ -0,0 +1,64 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.dialog.EditRepositoryDataDialog; +import info.novatec.inspectit.rcp.provider.IStorageDataProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageException; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Edit storage name and description handler. + * + * @author Ivan Senic + * + */ +public class EditStorageDataHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IStorageDataProvider storageDataProvider = null; + Object selectedElement = ((StructuredSelection) HandlerUtil.getCurrentSelection(event)).getFirstElement(); + if (selectedElement instanceof IStorageDataProvider) { + storageDataProvider = (IStorageDataProvider) selectedElement; + } else { + return null; + } + + StorageData storageData = storageDataProvider.getStorageData(); + EditRepositoryDataDialog editStorageDataDialog = new EditRepositoryDataDialog(HandlerUtil.getActiveShell(event), storageData.getName(), storageData.getDescription()); + editStorageDataDialog.open(); + if (editStorageDataDialog.getReturnCode() == EditRepositoryDataDialog.OK) { + CmrRepositoryDefinition cmrRepositoryDefinition = storageDataProvider.getCmrRepositoryDefinition(); + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + try { + storageData.setName(editStorageDataDialog.getName()); + storageData.setDescription(editStorageDataDialog.getDescription()); + cmrRepositoryDefinition.getStorageService().updateStorageData(storageData); + try { + InspectIT.getDefault().getInspectITStorageManager().storageRemotelyUpdated(storageData); + } catch (Exception e) { + InspectIT.getDefault().createErrorDialog("Storage data update failed.", e, -1); + } + } catch (StorageException e) { + InspectIT.getDefault().createErrorDialog("Storage data update failed.", e, -1); + } + } else { + InspectIT.getDefault().createInfoDialog("Storage data can not be updated, because the underlying repository is currently offline.", -1); + } + } + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/EmptyHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/EmptyHandler.java new file mode 100644 index 000000000..6ab0b8472 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/EmptyHandler.java @@ -0,0 +1,24 @@ +package info.novatec.inspectit.rcp.handlers; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; + +/** + * The empty handler is not doing anything when invoked. + * + * @author Ivan Senic + * + */ +public class EmptyHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/ExportLocalStorageHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/ExportLocalStorageHandler.java new file mode 100644 index 000000000..7bdd2f163 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/ExportLocalStorageHandler.java @@ -0,0 +1,40 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.provider.ILocalStorageDataProvider; +import info.novatec.inspectit.rcp.provider.IStorageDataProvider; +import info.novatec.inspectit.rcp.wizard.ExportStorageWizard; +import info.novatec.inspectit.storage.LocalStorageData; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Handler for exporting the local storage. + * + * @author Ivan Senic + * + */ +public class ExportLocalStorageHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + StructuredSelection structuredSelection = (StructuredSelection) HandlerUtil.getCurrentSelection(event); + Object selected = structuredSelection.getFirstElement(); + if (selected instanceof ILocalStorageDataProvider) { + LocalStorageData localStorageData = ((ILocalStorageDataProvider) selected).getLocalStorageData(); + new WizardDialog(HandlerUtil.getActiveShell(event), new ExportStorageWizard(localStorageData)).open(); + } else if (selected instanceof IStorageDataProvider) { + IStorageDataProvider storageDataProvider = (IStorageDataProvider) selected; + new WizardDialog(HandlerUtil.getActiveShell(event), new ExportStorageWizard(storageDataProvider.getStorageData(), storageDataProvider.getCmrRepositoryDefinition())).open(); + } + return null; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/FindHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/FindHandler.java new file mode 100644 index 000000000..995941d1a --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/FindHandler.java @@ -0,0 +1,94 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.editor.ISubView; +import info.novatec.inspectit.rcp.editor.composite.AbstractCompositeSubView; +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.editor.search.ISearchExecutor; +import info.novatec.inspectit.rcp.editor.search.OpenedSearchControlCache; +import info.novatec.inspectit.rcp.editor.search.SearchControl; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * The handler for activating the search box. + * + * @author Ivan Senic + * + */ +public class FindHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IEditorPart activeEditor = HandlerUtil.getActiveEditor(event); + if (activeEditor instanceof AbstractRootEditor) { + AbstractRootEditor abstractRootEditor = (AbstractRootEditor) activeEditor; + ISearchExecutor searchExecutor = null; + ISubView searchSubView = null; + + if (abstractRootEditor.getActiveSubView() instanceof ISearchExecutor) { + searchSubView = abstractRootEditor.getActiveSubView(); + searchExecutor = (ISearchExecutor) abstractRootEditor.getActiveSubView(); + } else { + searchSubView = findSearchExecutorView(abstractRootEditor.getSubView()); + searchExecutor = (ISearchExecutor) searchSubView; + } + + if (null != searchExecutor && null != searchSubView) { + ensureNoSearchOpened(abstractRootEditor.getSubView()); + new SearchControl(searchExecutor, HandlerUtil.getActiveShellChecked(event), searchSubView.getControl(), activeEditor); + } + } + return null; + } + + /** + * Ensures that no search control is opened for the given {@link ISubView}. If the view is + * composite, than it ensures than no child sub-view has control opened. + * + * @param subView + * SubView to check. + */ + private void ensureNoSearchOpened(ISubView subView) { + if (subView instanceof ISearchExecutor) { + SearchControl searchControl = OpenedSearchControlCache.getSearchControl((ISearchExecutor) subView); + if (null != searchControl) { + searchControl.closeControl(); + } + } else if (subView instanceof AbstractCompositeSubView) { + AbstractCompositeSubView compositeSubView = (AbstractCompositeSubView) subView; + for (ISubView viewInCompositeSubView : compositeSubView.getSubViews()) { + ensureNoSearchOpened(viewInCompositeSubView); + } + } + } + + /** + * Tries to find a {@link ISubView} that implement {@link ISearchExecutor} interface. + * + * @param subView + * {@link ISubView} to check. + * @return Sub-view. + */ + private ISubView findSearchExecutorView(ISubView subView) { + if (subView instanceof ISearchExecutor) { + return subView; + } else if (subView instanceof AbstractCompositeSubView) { + AbstractCompositeSubView compositeSubView = (AbstractCompositeSubView) subView; + for (ISubView viewInCompositeSubView : compositeSubView.getSubViews()) { + ISubView foundView = findSearchExecutorView(viewInCompositeSubView); + if (null != foundView) { + return foundView; + } + } + } + return null; + } +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/HttpDisplayInChartHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/HttpDisplayInChartHandler.java new file mode 100644 index 000000000..bdd89ed0c --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/HttpDisplayInChartHandler.java @@ -0,0 +1,124 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData.PartType; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition.IdDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.HttpChartingInputDefinitionExtra; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory; +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.editor.table.input.TaggedHttpTimerDataInputController; +import info.novatec.inspectit.rcp.model.SensorTypeEnum; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; +import info.novatec.inspectit.rcp.util.data.RegExAggregatedHttpTimerData; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.swt.widgets.Event; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.handlers.IHandlerService; + +/** + * Handler for displaying the {@link HttpTimerData} in charts. + * + * @author Ivan Senic + * + */ +public class HttpDisplayInChartHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelection(event); + AbstractRootEditor rootEditor = (AbstractRootEditor) HandlerUtil.getActiveEditor(event); + RepositoryDefinition repositoryDefinition = rootEditor.getInputDefinition().getRepositoryDefinition(); + InputDefinition inputDefinition = null; + + List templates = new ArrayList<>(); + List regExTemplates = new ArrayList<>(); + boolean regExTransformation = false; + for (Iterator iterator = selection.iterator(); iterator.hasNext();) { + Object selectedObject = iterator.next(); + if (selectedObject instanceof RegExAggregatedHttpTimerData) { + templates.addAll(((RegExAggregatedHttpTimerData) selectedObject).getAggregatedDataList()); + regExTemplates.add((RegExAggregatedHttpTimerData) selectedObject); + regExTransformation = true; + } else if (selectedObject instanceof HttpTimerData) { + templates.add((HttpTimerData) selectedObject); + } + } + + if (CollectionUtils.isNotEmpty(templates)) { + boolean plotByTagValue = null != rootEditor.getSubView().getSubViewWithInputController(TaggedHttpTimerDataInputController.class); + + inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(repositoryDefinition); + inputDefinition.setId(SensorTypeEnum.CHARTING_HTTP_TIMER_SENSOR); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(SensorTypeEnum.CHARTING_HTTP_TIMER_SENSOR.getImage()); + editorPropertiesData.setSensorName("Chart"); + editorPropertiesData.setPartNameFlag(PartType.SENSOR); + if (templates.size() == 1 && !regExTransformation) { + HttpTimerData template = templates.get(0); + if (plotByTagValue) { + editorPropertiesData.setViewName("Tag: " + template.getInspectItTaggingHeaderValue() + " [" + template.getRequestMethod() + "]"); + } else { + editorPropertiesData.setViewName("URI: " + template.getUri() + " [" + template.getRequestMethod() + "]"); + } + } else if (regExTemplates.size() == 1 && regExTransformation) { + RegExAggregatedHttpTimerData regExTemplate = regExTemplates.get(0); + editorPropertiesData.setViewName("Transformed URI: " + regExTemplate.getTransformedUri() + " [" + regExTemplate.getRequestMethod() + "]"); + } else { + editorPropertiesData.setViewName("Multiple HTTP data"); + } + + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(templates.get(0).getPlatformIdent()); + idDefinition.setSensorTypeId(templates.get(0).getSensorTypeIdent()); + inputDefinition.setIdDefinition(idDefinition); + + HttpChartingInputDefinitionExtra inputDefinitionExtra = new HttpChartingInputDefinitionExtra(); + inputDefinitionExtra.setTemplates(templates); + inputDefinitionExtra.setRegExTemplates(regExTemplates); + inputDefinitionExtra.setPlotByTagValue(plotByTagValue); + inputDefinition.addInputDefinitonExtra(InputDefinitionExtrasMarkerFactory.HTTP_CHARTING_EXTRAS_MARKER, inputDefinitionExtra); + + // open the view via command + IHandlerService handlerService = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); + ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + + Command command = commandService.getCommand(OpenViewHandler.COMMAND); + ExecutionEvent executionEvent = handlerService.createExecutionEvent(command, new Event()); + IEvaluationContext context = (IEvaluationContext) executionEvent.getApplicationContext(); + context.addVariable(OpenViewHandler.INPUT, inputDefinition); + + try { + command.executeWithChecks(executionEvent); + } catch (Exception e) { + InspectIT.getDefault().createErrorDialog(e.getMessage(), e, -1); + } + } + + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/InvocationsCombineDataHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/InvocationsCombineDataHandler.java new file mode 100644 index 000000000..2b641149b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/InvocationsCombineDataHandler.java @@ -0,0 +1,99 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData.PartType; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition.IdDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.CombinedInvocationsInputDefinitionExtra; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory; +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.model.SensorTypeEnum; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.swt.widgets.Event; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.handlers.IHandlerService; + +/** + * + * @author Ivan Senic + * + */ +public class InvocationsCombineDataHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelection(event); + AbstractRootEditor rootEditor = (AbstractRootEditor) HandlerUtil.getActiveEditor(event); + RepositoryDefinition repositoryDefinition = rootEditor.getInputDefinition().getRepositoryDefinition(); + InputDefinition inputDefinition = null; + + long platformIdent = 0; + List invocationsList = new ArrayList(); + for (Iterator it = selection.iterator(); it.hasNext();) { + Object nextObject = it.next(); + if (nextObject instanceof InvocationSequenceData) { + platformIdent = ((InvocationSequenceData) nextObject).getPlatformIdent(); + invocationsList.add((InvocationSequenceData) nextObject); + } + } + + if (!invocationsList.isEmpty()) { + String desc = "Aggregated data found in " + invocationsList.size() + " selected invocations"; + + inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(repositoryDefinition); + inputDefinition.setId(SensorTypeEnum.MULTI_INVOC_DATA); + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(SensorTypeEnum.INVOCATION_SEQUENCE.getImage()); + editorPropertiesData.setSensorName("Invocation Sequences"); + editorPropertiesData.setViewName(desc); + editorPropertiesData.setPartNameFlag(PartType.SENSOR); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(platformIdent); + inputDefinition.setIdDefinition(idDefinition); + + CombinedInvocationsInputDefinitionExtra combinedInvocationsInputDefinitionExtra = new CombinedInvocationsInputDefinitionExtra(); + combinedInvocationsInputDefinitionExtra.setTemplates(invocationsList); + inputDefinition.addInputDefinitonExtra(InputDefinitionExtrasMarkerFactory.COMBINED_INVOCATIONS_EXTRAS_MARKER, combinedInvocationsInputDefinitionExtra); + + // open the view via command + IHandlerService handlerService = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); + ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + + Command command = commandService.getCommand(OpenViewHandler.COMMAND); + ExecutionEvent executionEvent = handlerService.createExecutionEvent(command, new Event()); + IEvaluationContext context = (IEvaluationContext) executionEvent.getApplicationContext(); + context.addVariable(OpenViewHandler.INPUT, inputDefinition); + + try { + command.executeWithChecks(executionEvent); + } catch (Exception e) { + InspectIT.getDefault().createErrorDialog(e.getMessage(), e, -1); + } + } + + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/LocateHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/LocateHandler.java new file mode 100644 index 000000000..ce7b93a9f --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/LocateHandler.java @@ -0,0 +1,199 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.editor.ISubView; +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.editor.tree.SteppingTreeSubView; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Abstract handler for all other handlers that are working with locate functionality. + * + * @author Ivan Senic + * + */ +public abstract class LocateHandler extends AbstractTemplateHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IEditorPart activeEditor = HandlerUtil.getActiveEditor(event); + if (activeEditor instanceof AbstractRootEditor) { + AbstractRootEditor rootEditor = (AbstractRootEditor) activeEditor; + ISubView mainView = rootEditor.getSubView(); + SteppingTreeSubView steppingTreeSubView = mainView.getSubView(SteppingTreeSubView.class); + if (steppingTreeSubView != null) { + StructuredSelection structuredSelection = (StructuredSelection) HandlerUtil.getCurrentSelection(event); + List templates = this.getTemplates(structuredSelection); + for (DefaultData objectToLocate : templates) { + steppingTreeSubView.addObjectToSteppingControl(objectToLocate); + } + + // switch this to tree tab + mainView.select(steppingTreeSubView); + } + } + return null; + } + + /** + * Return {@link AbstractTemplateDefinitionDialog}. + * + * @param structuredSelection + * Current {@link StructuredSelection}. + * + * @return {@link AbstractTemplateDefinitionDialog}. + */ + public abstract List getTemplates(StructuredSelection structuredSelection); + + /** + * {@link LocateHandler} for {@link info.novatec.inspectit.communication.data.SqlStatementData}. + * + * @author Ivan Senic + * + */ + public static final class SqlLocateHandler extends LocateHandler { + + /** + * {@inheritDoc} + */ + @Override + public List getTemplates(StructuredSelection structuredSelection) { + List results = new ArrayList(); + for (Object selected : structuredSelection.toList()) { + if (selected instanceof SqlStatementData) { + results.add(super.getTemplate((SqlStatementData) selected, true, true, true)); + } + } + return results; + } + + } + + /** + * {@link LocateHandler} for + * {@link info.novatec.inspectit.communication.data.ExceptionSensorData}. + * + * @author Ivan Senic + * + */ + public static final class ExceptionLocateHandler extends LocateHandler { + + /** + * {@inheritDoc} + */ + @Override + public List getTemplates(StructuredSelection structuredSelection) { + List results = new ArrayList(); + for (Object selected : structuredSelection.toList()) { + if (selected instanceof ExceptionSensorData) { + results.add(super.getTemplate((ExceptionSensorData) selected, true, true, true, true, true)); + } + } + return results; + } + + } + + /** + * {@link LocateHandler} for + * {@link info.novatec.inspectit.communication.data.InvocationSequenceData}. + * + * @author Ivan Senic + * + */ + public static final class InvocationLocateHandler extends LocateHandler { + + /** + * {@inheritDoc} + */ + @Override + public List getTemplates(StructuredSelection structuredSelection) { + List results = new ArrayList(); + for (Object selected : structuredSelection.toList()) { + if (selected instanceof InvocationSequenceData) { + InvocationSequenceData invocationSequenceData = (InvocationSequenceData) selected; + + if (null != invocationSequenceData.getSqlStatementData()) { + // if we have SQL add it to list without parameters, cause we want to find + // same in invocation tree + results.add(super.getTemplate(invocationSequenceData.getSqlStatementData(), false, true, false)); + + // add also a timer data or created timer data, so that we can find method + if (null != invocationSequenceData.getTimerData() && TimerData.class.equals(invocationSequenceData.getTimerData().getClass())) { + results.add(super.getTemplate(invocationSequenceData.getTimerData(), false, true)); + } else { + results.add(super.getTemplate(getTimerDataForSql(invocationSequenceData.getSqlStatementData()), false, true)); + } + } else if (null != invocationSequenceData.getExceptionSensorDataObjects() && !invocationSequenceData.getExceptionSensorDataObjects().isEmpty()) { + // locate all exceptions of the same throwable type + // we don't care here for stake trace, message and exception event + ExceptionSensorData data = invocationSequenceData.getExceptionSensorDataObjects().get(0); + results.add(super.getTemplate(data, false, true, false, false, false)); + } else if (null != invocationSequenceData.getTimerData() && TimerData.class.equals(invocationSequenceData.getTimerData().getClass())) { + // if we have timer and not http timer data add + results.add(super.getTemplate(invocationSequenceData.getTimerData(), false, true)); + } else { + // at the end if we have nothing add the invocation itself + results.add(super.getTemplate(invocationSequenceData, false, true)); + } + } + } + return results; + } + + /** + * Returns {@link TimerData} instance with the same method ident as given + * {@link SqlStatementData}. + * + * @param sqlStatementData + * {@link SqlStatementData}. + * @return Returns {@link TimerData} instance with the same method ident as given + * {@link SqlStatementData}. + */ + private TimerData getTimerDataForSql(SqlStatementData sqlStatementData) { + TimerData timerData = new TimerData(); + timerData.setMethodIdent(sqlStatementData.getMethodIdent()); + return timerData; + } + + } + + /** + * {@link LocateHandler} for {@link info.novatec.inspectit.communication.data.TimerData}. + * + * @author Ivan Senic + * + */ + public static final class TimerLocateHandler extends LocateHandler { + + /** + * {@inheritDoc} + */ + @Override + public List getTemplates(StructuredSelection structuredSelection) { + List results = new ArrayList(); + for (Object selected : structuredSelection.toList()) { + if (selected instanceof TimerData) { + results.add(super.getTemplate((TimerData) selected, true, true)); + } + } + return results; + } + + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/MaximizeActiveViewHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/MaximizeActiveViewHandler.java new file mode 100644 index 000000000..44d17e7f2 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/MaximizeActiveViewHandler.java @@ -0,0 +1,86 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.editor.preferences.IPreferencePanel; +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.commands.IElementUpdater; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.menus.UIElement; +import org.eclipse.ui.services.IServiceScopes; + +/** + * Handler for the maximize/minimize the active sub-view. At the same time this Handler implements + * the {@link IElementUpdater} interface so that we can manually update the checked state of the UI + * elements that are bounded to the {@value #COMMAND_ID} command. + * + * @author Ivan Senic + * + */ +public class MaximizeActiveViewHandler extends AbstractHandler implements IHandler, IElementUpdater { + + /** + * Command id. + */ + public static final String COMMAND_ID = "info.novatec.inspectit.rcp.commands.maximizeActiveView"; + + /** + * Preference panel id parameter needed for this command. + */ + public static final String PREFERENCE_PANEL_ID_PARAMETER = COMMAND_ID + ".preferencePanelId"; + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IEditorPart editorPart = HandlerUtil.getActiveEditor(event); + if (editorPart instanceof AbstractRootEditor) { + AbstractRootEditor abstractRootEditor = (AbstractRootEditor) editorPart; + if (abstractRootEditor.canMaximizeActiveSubView()) { + abstractRootEditor.maximizeActiveSubView(); + } else if (abstractRootEditor.canMinimizeActiveSubView()) { + abstractRootEditor.minimizeActiveSubView(); + } + } + + // after the maximized/minimized is executed we need to refresh the UI elements bounded to + // the command, so that checked state of that elements is updated + ICommandService commandService = (ICommandService) HandlerUtil.getActiveWorkbenchWindow(event).getService(ICommandService.class); + Map filter = new HashMap(); + filter.put(IServiceScopes.WINDOW_SCOPE, HandlerUtil.getActiveWorkbenchWindow(event)); + commandService.refreshElements(event.getCommand().getId(), filter); + return null; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("rawtypes") + @Override + public void updateElement(UIElement element, Map parameters) { + // we'll only update the element that is bounded to the preference panel in the active + // sub-view + IWorkbenchWindow workbenchWindow = (IWorkbenchWindow) parameters.get("org.eclipse.ui.IWorkbenchWindow"); + String preferencePanelId = (String) parameters.get(PREFERENCE_PANEL_ID_PARAMETER); + if (null != workbenchWindow && null != preferencePanelId) { + IEditorPart editorPart = workbenchWindow.getActivePage().getActiveEditor(); + if (editorPart instanceof AbstractRootEditor) { + AbstractRootEditor abstractRootEditor = (AbstractRootEditor) editorPart; + IPreferencePanel preferencePanel = abstractRootEditor.getPreferencePanel(); + if (preferencePanelId.equals(preferencePanel.getId())) { + element.setChecked(!abstractRootEditor.canMaximizeActiveSubView()); + } + } + } + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToAggregatedSqlDataHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToAggregatedSqlDataHandler.java new file mode 100644 index 000000000..9c2cfac4c --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToAggregatedSqlDataHandler.java @@ -0,0 +1,93 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData.PartType; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition.IdDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.SqlStatementInputDefinitionExtra; +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.model.SensorTypeEnum; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.widgets.Event; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.handlers.IHandlerService; + +/** + * Handler for navigation to the aggregated SQL data. + * + * @author Ivan Senic + * + */ +public class NavigateToAggregatedSqlDataHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + StructuredSelection selection = (StructuredSelection) HandlerUtil.getCurrentSelectionChecked(event); + AbstractRootEditor rootEditor = (AbstractRootEditor) HandlerUtil.getActiveEditor(event); + RepositoryDefinition repositoryDefinition = rootEditor.getInputDefinition().getRepositoryDefinition(); + + Object selectedObject = selection.getFirstElement(); + SqlStatementData dataToNavigateTo = null; + if (selectedObject instanceof SqlStatementData) { + dataToNavigateTo = (SqlStatementData) selectedObject; + } else if (selectedObject instanceof InvocationSequenceData) { + dataToNavigateTo = ((InvocationSequenceData) selectedObject).getSqlStatementData(); + } + + if (null != dataToNavigateTo) { + InputDefinition inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(repositoryDefinition); + inputDefinition.setId(SensorTypeEnum.SQL); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(SensorTypeEnum.SQL.getImage()); + editorPropertiesData.setSensorName("Aggregated SQL Statements"); + editorPropertiesData.setViewName(dataToNavigateTo.getSql()); + editorPropertiesData.setPartNameFlag(PartType.SENSOR); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(dataToNavigateTo.getPlatformIdent()); + inputDefinition.setIdDefinition(idDefinition); + + SqlStatementInputDefinitionExtra sqlStatementInputDefinitionExtra = new SqlStatementInputDefinitionExtra(); + sqlStatementInputDefinitionExtra.setSql(dataToNavigateTo.getSql()); + inputDefinition.addInputDefinitonExtra(InputDefinitionExtrasMarkerFactory.SQL_STATEMENT_EXTRAS_MARKER, sqlStatementInputDefinitionExtra); + + // open the view via command + IHandlerService handlerService = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); + ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + + Command command = commandService.getCommand(OpenViewHandler.COMMAND); + ExecutionEvent executionEvent = handlerService.createExecutionEvent(command, new Event()); + IEvaluationContext context = (IEvaluationContext) executionEvent.getApplicationContext(); + context.addVariable(OpenViewHandler.INPUT, inputDefinition); + + try { + command.executeWithChecks(executionEvent); + } catch (Exception e) { + InspectIT.getDefault().createErrorDialog(e.getMessage(), e, -1); + } + } + + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToAggregatedTimerDataHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToAggregatedTimerDataHandler.java new file mode 100644 index 000000000..c59a37e22 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToAggregatedTimerDataHandler.java @@ -0,0 +1,93 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData.PartType; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition.IdDefinition; +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.model.ModifiersImageFactory; +import info.novatec.inspectit.rcp.model.SensorTypeEnum; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.widgets.Event; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.handlers.IHandlerService; + +/** + * Handler for navigation to the aggregated Timer data. + * + * @author Ivan Senic + * + */ +public class NavigateToAggregatedTimerDataHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + StructuredSelection selection = (StructuredSelection) HandlerUtil.getCurrentSelectionChecked(event); + AbstractRootEditor rootEditor = (AbstractRootEditor) HandlerUtil.getActiveEditor(event); + RepositoryDefinition repositoryDefinition = rootEditor.getInputDefinition().getRepositoryDefinition(); + + Object selectedObject = selection.getFirstElement(); + TimerData dataToNavigateTo = null; + if (selectedObject instanceof TimerData) { + dataToNavigateTo = (TimerData) selectedObject; + } else if (selectedObject instanceof InvocationSequenceData) { + dataToNavigateTo = ((InvocationSequenceData) selectedObject).getTimerData(); + } + + if (null != dataToNavigateTo) { + MethodIdent methodIdent = repositoryDefinition.getCachedDataService().getMethodIdentForId(dataToNavigateTo.getMethodIdent()); + + InputDefinition inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(repositoryDefinition); + inputDefinition.setId(SensorTypeEnum.TIMER); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorName(SensorTypeEnum.TIMER.getDisplayName()); + editorPropertiesData.setSensorImage(SensorTypeEnum.TIMER.getImage()); + editorPropertiesData.setViewName(TextFormatter.getMethodString(methodIdent)); + editorPropertiesData.setViewImage(ModifiersImageFactory.getImage(methodIdent.getModifiers())); + editorPropertiesData.setPartNameFlag(PartType.SENSOR); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(dataToNavigateTo.getPlatformIdent()); + idDefinition.setMethodId(dataToNavigateTo.getMethodIdent()); + inputDefinition.setIdDefinition(idDefinition); + + // open the view via command + IHandlerService handlerService = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); + ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + + Command command = commandService.getCommand(OpenViewHandler.COMMAND); + ExecutionEvent executionEvent = handlerService.createExecutionEvent(command, new Event()); + IEvaluationContext context = (IEvaluationContext) executionEvent.getApplicationContext(); + context.addVariable(OpenViewHandler.INPUT, inputDefinition); + + try { + command.executeWithChecks(executionEvent); + } catch (Exception e) { + InspectIT.getDefault().createErrorDialog(e.getMessage(), e, -1); + } + } + + return null; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToExceptionTypeHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToExceptionTypeHandler.java new file mode 100644 index 000000000..f7b28b0fb --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToExceptionTypeHandler.java @@ -0,0 +1,176 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData.PartType; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition.IdDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.ExceptionTypeInputDefinitionExtra; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory; +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.model.SensorTypeEnum; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.List; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.widgets.Event; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.handlers.IHandlerService; + +/** + * Handler that opens the only concrete exception type view. + * + * @author Ivan Senic + * + */ +public abstract class NavigateToExceptionTypeHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + StructuredSelection selection = (StructuredSelection) HandlerUtil.getCurrentSelectionChecked(event); + AbstractRootEditor rootEditor = (AbstractRootEditor) HandlerUtil.getActiveEditor(event); + RepositoryDefinition repositoryDefinition = rootEditor.getInputDefinition().getRepositoryDefinition(); + + Object selectedObject = selection.getFirstElement(); + ExceptionSensorData dataToNavigateTo = null; + if (selectedObject instanceof ExceptionSensorData) { + dataToNavigateTo = (ExceptionSensorData) selectedObject; + } else if (selectedObject instanceof InvocationSequenceData) { + List exceptions = ((InvocationSequenceData) selectedObject).getExceptionSensorDataObjects(); + if (null != exceptions && !exceptions.isEmpty()) { + for (ExceptionSensorData exSensorData : exceptions) { + if (0 != exSensorData.getMethodIdent()) { + dataToNavigateTo = exSensorData; + break; + } + } + } + } + + if (null != dataToNavigateTo) { + ExceptionSensorData exceptionSensorData = dataToNavigateTo; + + // exit if the object does not carry the methodIdent + if (null == exceptionSensorData.getThrowableType()) { + return null; + } + + InputDefinition inputDefinition = getInputDefinition(repositoryDefinition, exceptionSensorData); + + // open the view via command + IHandlerService handlerService = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); + ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + + Command command = commandService.getCommand(OpenViewHandler.COMMAND); + ExecutionEvent executionEvent = handlerService.createExecutionEvent(command, new Event()); + IEvaluationContext context = (IEvaluationContext) executionEvent.getApplicationContext(); + context.addVariable(OpenViewHandler.INPUT, inputDefinition); + + try { + command.executeWithChecks(executionEvent); + } catch (Exception e) { + InspectIT.getDefault().createErrorDialog(e.getMessage(), e, -1); + } + } + return null; + } + + /** + * Returns input definition. Sub-classes should only implement this method. + * + * @param repositoryDefinition + * {@link RepositoryDefinition} + * @param exceptionSensorData + * Data to navigate to. + * @return {@link InputDefinition} + */ + protected abstract InputDefinition getInputDefinition(RepositoryDefinition repositoryDefinition, ExceptionSensorData exceptionSensorData); + + /** + * Handler for navigating to single exception view. + * + * @author Ivan Senic + * + */ + public static final class NavigateToSingleExceptionTypeHandler extends NavigateToExceptionTypeHandler { + + /** + * {@inheritDoc} + */ + @Override + protected InputDefinition getInputDefinition(RepositoryDefinition repositoryDefinition, ExceptionSensorData exceptionSensorData) { + InputDefinition inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(repositoryDefinition); + inputDefinition.setId(SensorTypeEnum.EXCEPTION_SENSOR); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(SensorTypeEnum.EXCEPTION_SENSOR.getImage()); + editorPropertiesData.setSensorName(SensorTypeEnum.EXCEPTION_SENSOR.getDisplayName()); + editorPropertiesData.setViewName(exceptionSensorData.getThrowableType()); + editorPropertiesData.setPartNameFlag(PartType.SENSOR); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(exceptionSensorData.getPlatformIdent()); + inputDefinition.setIdDefinition(idDefinition); + + ExceptionTypeInputDefinitionExtra exceptionTypeInputDefinitionExtra = new ExceptionTypeInputDefinitionExtra(); + exceptionTypeInputDefinitionExtra.setThrowableType(exceptionSensorData.getThrowableType()); + inputDefinition.addInputDefinitonExtra(InputDefinitionExtrasMarkerFactory.EXCEPTION_TYPE_EXTRAS_MARKER, exceptionTypeInputDefinitionExtra); + + return inputDefinition; + } + + } + + /** + * Handler for navigating to grouped exception view. + * + * @author Ivan Senic + * + */ + public static final class NavigateToGroupedExceptionTypeHandler extends NavigateToExceptionTypeHandler { + + /** + * {@inheritDoc} + */ + @Override + protected InputDefinition getInputDefinition(RepositoryDefinition repositoryDefinition, ExceptionSensorData exceptionSensorData) { + InputDefinition inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(repositoryDefinition); + inputDefinition.setId(SensorTypeEnum.EXCEPTION_SENSOR_GROUPED); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(SensorTypeEnum.EXCEPTION_SENSOR_GROUPED.getImage()); + editorPropertiesData.setSensorName(SensorTypeEnum.EXCEPTION_SENSOR_GROUPED.getDisplayName()); + editorPropertiesData.setViewName(exceptionSensorData.getThrowableType()); + editorPropertiesData.setPartNameFlag(PartType.SENSOR); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(exceptionSensorData.getPlatformIdent()); + inputDefinition.setIdDefinition(idDefinition); + + ExceptionTypeInputDefinitionExtra exceptionTypeInputDefinitionExtra = new ExceptionTypeInputDefinitionExtra(); + exceptionTypeInputDefinitionExtra.setThrowableType(exceptionSensorData.getThrowableType()); + inputDefinition.addInputDefinitonExtra(InputDefinitionExtrasMarkerFactory.EXCEPTION_TYPE_EXTRAS_MARKER, exceptionTypeInputDefinitionExtra); + + return inputDefinition; + } + + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToInvocationsHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToInvocationsHandler.java new file mode 100644 index 000000000..db0abebfe --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToInvocationsHandler.java @@ -0,0 +1,154 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.InvocationAwareData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData.PartType; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition.IdDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.NavigationSteppingInputDefinitionExtra; +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.model.SensorTypeEnum; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.swt.widgets.Event; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.handlers.IHandlerService; + +/** + * Handler for navigating from table view that contains {@link IInvocationAwareData}, to the + * invocation sequence view. + * + * @author Ivan Senic + * + */ +public class NavigateToInvocationsHandler extends AbstractTemplateHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelection(event); + AbstractRootEditor rootEditor = (AbstractRootEditor) HandlerUtil.getActiveEditor(event); + RepositoryDefinition repositoryDefinition = rootEditor.getInputDefinition().getRepositoryDefinition(); + InputDefinition inputDefinition = null; + + List invocationAwareDataList = new ArrayList(); + String textualDesc = null; + int selectionSize = selection.size(); + int invocationsCount = 0; + long platformIdent = getPlatformIdent(selection.getFirstElement()); + + if (selectionSize > 1) { + textualDesc = "multiple selected objects"; + } + for (Iterator iterator = selection.iterator(); iterator.hasNext();) { + Object selectedObject = iterator.next(); + if (selectedObject instanceof InvocationAwareData) { + InvocationAwareData invocationAwareData = (InvocationAwareData) selectedObject; + if (1 == selectionSize) { + textualDesc = TextFormatter.getInvocationAwareDataTextualRepresentation(invocationAwareData, repositoryDefinition); + } + if (null != invocationAwareData.getInvocationParentsIdSet()) { + invocationAwareDataList.add(invocationAwareData); + invocationsCount += invocationAwareData.getInvocationParentsIdSet().size(); + } + } + } + + if (invocationsCount > 0) { + inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(repositoryDefinition); + inputDefinition.setId(SensorTypeEnum.NAVIGATION_INVOCATION); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(SensorTypeEnum.INVOCATION_SEQUENCE.getImage()); + editorPropertiesData.setSensorName(SensorTypeEnum.INVOCATION_SEQUENCE.getDisplayName()); + editorPropertiesData.setViewName("that contain " + textualDesc); + editorPropertiesData.setPartNameFlag(PartType.SENSOR); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(platformIdent); + inputDefinition.setIdDefinition(idDefinition); + + NavigationSteppingInputDefinitionExtra navigationSteppingExtra = new NavigationSteppingInputDefinitionExtra(); + navigationSteppingExtra.setInvocationAwareDataList(invocationAwareDataList); + navigationSteppingExtra.setSteppingTemplateList(getTemplates(invocationAwareDataList)); + inputDefinition.addInputDefinitonExtra(InputDefinitionExtrasMarkerFactory.NAVIGATION_STEPPING_EXTRAS_MARKER, navigationSteppingExtra); + + // open the view via command + IHandlerService handlerService = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); + ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + + Command command = commandService.getCommand(OpenViewHandler.COMMAND); + ExecutionEvent executionEvent = handlerService.createExecutionEvent(command, new Event()); + IEvaluationContext context = (IEvaluationContext) executionEvent.getApplicationContext(); + context.addVariable(OpenViewHandler.INPUT, inputDefinition); + + try { + command.executeWithChecks(executionEvent); + } catch (Exception e) { + InspectIT.getDefault().createErrorDialog(e.getMessage(), e, -1); + } + } + + return null; + } + + /** + * Creates a steppable template from a list of {@link InvocationAwareData}. + * + * @param invocationAwareDataList + * {@link InvocationAwareData} list. + * @return Templates to be used as steppable objects. + */ + private List getTemplates(List invocationAwareDataList) { + List steppableTemplates = new ArrayList(); + for (InvocationAwareData invocationAwareData : invocationAwareDataList) { + if (invocationAwareData instanceof SqlStatementData) { + steppableTemplates.add(super.getTemplate((SqlStatementData) invocationAwareData, false, true, true)); + } else if (invocationAwareData instanceof TimerData && !(invocationAwareData instanceof HttpTimerData)) { + steppableTemplates.add(super.getTemplate((TimerData) invocationAwareData, false, true)); + } else if (invocationAwareData instanceof ExceptionSensorData) { + steppableTemplates.add(super.getTemplate((ExceptionSensorData) invocationAwareData, false, true, true, true, true)); + } + } + return steppableTemplates; + } + + /** + * Returns the platform id for the object. + * + * @param firstElement + * Object. + * @return If object is instance of {@link DefaultData} method returns its platform id, + * otherwise 0. + */ + private long getPlatformIdent(Object firstElement) { + if (firstElement instanceof DefaultData) { + return ((DefaultData) firstElement).getPlatformIdent(); + } + return 0; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToPlotting.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToPlotting.java new file mode 100644 index 000000000..e348a56bf --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToPlotting.java @@ -0,0 +1,128 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData.PartType; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition.IdDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory; +import info.novatec.inspectit.rcp.editor.inputdefinition.extra.TimerDataChartingInputDefinitionExtra; +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.model.ModifiersImageFactory; +import info.novatec.inspectit.rcp.model.SensorTypeEnum; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.swt.widgets.Event; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.handlers.IHandlerService; + +/** + * Handler for navigation from the aggregated timer data to the plotting. + * + * @author Ivan Senic + * + */ +public class NavigateToPlotting extends AbstractHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelection(event); + AbstractRootEditor rootEditor = (AbstractRootEditor) HandlerUtil.getActiveEditor(event); + RepositoryDefinition repositoryDefinition = rootEditor.getInputDefinition().getRepositoryDefinition(); + InputDefinition inputDefinition = null; + List templates = new ArrayList<>(); + + for (Iterator it = selection.iterator(); it.hasNext();) { + Object selectedObject = it.next(); + TimerData timerData = null; + if (selectedObject instanceof TimerData) { + timerData = (TimerData) selectedObject; + } else if (selectedObject instanceof InvocationSequenceData) { + InvocationSequenceData invoc = (InvocationSequenceData) selectedObject; + if (invoc.getTimerData() != null) { + timerData = invoc.getTimerData(); + } + } + + if (null != timerData) { + TimerData template = new TimerData(null, timerData.getPlatformIdent(), timerData.getSensorTypeIdent(), timerData.getMethodIdent()); + templates.add(template); + } + } + + if (CollectionUtils.isEmpty(templates)) { + return null; + } + + inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(repositoryDefinition); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(SensorTypeEnum.CHARTING_TIMER.getImage()); + editorPropertiesData.setSensorName("Chart"); + editorPropertiesData.setPartNameFlag(PartType.SENSOR); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(templates.get(0).getPlatformIdent()); + inputDefinition.setIdDefinition(idDefinition); + + if (templates.size() == 1) { + TimerData timerData = templates.get(0); + MethodIdent methodIdent = repositoryDefinition.getCachedDataService().getMethodIdentForId(timerData.getMethodIdent()); + + editorPropertiesData.setViewImage(ModifiersImageFactory.getImage(methodIdent.getModifiers())); + editorPropertiesData.setViewName(TextFormatter.getMethodString(methodIdent)); + + inputDefinition.setId(SensorTypeEnum.CHARTING_TIMER); + idDefinition.setPlatformId(timerData.getPlatformIdent()); + idDefinition.setSensorTypeId(timerData.getSensorTypeIdent()); + idDefinition.setMethodId(timerData.getMethodIdent()); + } else { + editorPropertiesData.setViewName("Multiple Timer data"); + + TimerDataChartingInputDefinitionExtra definitionExtra = new TimerDataChartingInputDefinitionExtra(); + definitionExtra.setTemplates(templates); + inputDefinition.setId(SensorTypeEnum.CHARTING_MULTI_TIMER); + inputDefinition.addInputDefinitonExtra(InputDefinitionExtrasMarkerFactory.TIMER_DATA_CHARTING_EXTRAS_MARKER, definitionExtra); + } + + // open the view via command + IHandlerService handlerService = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); + ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + + Command command = commandService.getCommand(OpenViewHandler.COMMAND); + ExecutionEvent executionEvent = handlerService.createExecutionEvent(command, new Event()); + IEvaluationContext context = (IEvaluationContext) executionEvent.getApplicationContext(); + context.addVariable(OpenViewHandler.INPUT, inputDefinition); + + try { + command.executeWithChecks(executionEvent); + } catch (Exception e) { + InspectIT.getDefault().createErrorDialog(e.getMessage(), e, -1); + } + + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToStartMethodInvocationHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToStartMethodInvocationHandler.java new file mode 100644 index 000000000..fbd6e7414 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/NavigateToStartMethodInvocationHandler.java @@ -0,0 +1,87 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData.PartType; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition.IdDefinition; +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.model.ModifiersImageFactory; +import info.novatec.inspectit.rcp.model.SensorTypeEnum; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.widgets.Event; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.handlers.IHandlerService; + +/** + * Handler for showing only invocations sequences for a specific method ID. + * + * @author Ivan Senic + * + */ +public class NavigateToStartMethodInvocationHandler extends AbstractHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + StructuredSelection selection = (StructuredSelection) HandlerUtil.getCurrentSelectionChecked(event); + AbstractRootEditor rootEditor = (AbstractRootEditor) HandlerUtil.getActiveEditor(event); + RepositoryDefinition repositoryDefinition = rootEditor.getInputDefinition().getRepositoryDefinition(); + + Object selectedObject = selection.getFirstElement(); + if (selectedObject instanceof InvocationSequenceData) { + InvocationSequenceData invocationSequenceData = (InvocationSequenceData) selectedObject; + + MethodIdent methodIdent = repositoryDefinition.getCachedDataService().getMethodIdentForId(invocationSequenceData.getMethodIdent()); + + InputDefinition inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(repositoryDefinition); + inputDefinition.setId(SensorTypeEnum.INVOCATION_SEQUENCE); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(SensorTypeEnum.INVOCATION_SEQUENCE.getImage()); + editorPropertiesData.setSensorName(SensorTypeEnum.INVOCATION_SEQUENCE.getDisplayName()); + editorPropertiesData.setViewImage(ModifiersImageFactory.getImage(methodIdent.getModifiers())); + editorPropertiesData.setViewName(TextFormatter.getMethodString(methodIdent)); + editorPropertiesData.setPartNameFlag(PartType.SENSOR); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(invocationSequenceData.getPlatformIdent()); + idDefinition.setMethodId(invocationSequenceData.getMethodIdent()); + idDefinition.setSensorTypeId(invocationSequenceData.getSensorTypeIdent()); + + inputDefinition.setIdDefinition(idDefinition); + + // open the view via command + IHandlerService handlerService = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); + ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + + Command command = commandService.getCommand(OpenViewHandler.COMMAND); + ExecutionEvent executionEvent = handlerService.createExecutionEvent(command, new Event()); + IEvaluationContext context = (IEvaluationContext) executionEvent.getApplicationContext(); + context.addVariable(OpenViewHandler.INPUT, inputDefinition); + + try { + command.executeWithChecks(executionEvent); + } catch (Exception e) { + InspectIT.getDefault().createErrorDialog(e.getMessage(), e, -1); + } + } + return null; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/OpenUrlHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/OpenUrlHandler.java new file mode 100644 index 000000000..2c3195640 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/OpenUrlHandler.java @@ -0,0 +1,137 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.InspectIT; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.browser.IWebBrowser; +import org.eclipse.ui.browser.IWorkbenchBrowserSupport; + +/** + * Handler that opens that InspectIT Documentation page on Confluence. + * + * @author Ivan Senic + * + */ +public abstract class OpenUrlHandler extends AbstractHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IWorkbenchBrowserSupport browserSupport = PlatformUI.getWorkbench().getBrowserSupport(); + try { + IWebBrowser browser = browserSupport.createBrowser(null); + URL url = new URL(getUrlString(event)); + browser.openURL(url); + } catch (PartInitException e) { + InspectIT.getDefault().createErrorDialog(e.getMessage(), e, -1); + } catch (MalformedURLException e) { + InspectIT.getDefault().createErrorDialog(e.getMessage(), e, -1); + } + return null; + } + + /** + * Implementing classes should return the correct URL string to open. + * + * @param event + * {@link ExecutionEvent} that activated the handler. + * @return URL as a string. + */ + protected abstract String getUrlString(ExecutionEvent event); + + /** + * Handler for opening the Confluence documentation. + * + * @author Ivan Senic + * + */ + public static class OpenDocumentationHandler extends OpenUrlHandler { + + /** + * {@inheritDoc} + */ + @Override + protected String getUrlString(ExecutionEvent event) { + return "https://documentation.novatec-gmbh.de/display/INSPECTIT/Home"; + } + } + + /** + * Handler for staring the feedback email. + * + * @author Ivan Senic + * + */ + public static class GiveFeedbackHandler extends OpenUrlHandler { + + /** + * {@inheritDoc} + */ + @Override + protected String getUrlString(ExecutionEvent event) { + return "mailto:info.inspectit@novatec-gmbh.de&subject=Feedback"; + } + } + + /** + * Handler for staring the support email. + * + * @author Ivan Senic + * + */ + public static class RequestSupportHandler extends OpenUrlHandler { + + /** + * {@inheritDoc} + */ + @Override + protected String getUrlString(ExecutionEvent event) { + return "mailto:support.inspectit@novatec-gmbh.de&subject=Support%20needed"; + } + } + + /** + * Handler for searching the documentation. + * + * @author Ivan Senic + * + */ + public static class SearchDocumentationHandler extends OpenDocumentationHandler { + + /** + * Parameter for the SearchDocumentationHandler. + */ + public static final String SEARCH_DOCUMENTATION_PARAMETER = "info.novatec.inspectit.rcp.commands.searchDocumentation.searchString"; + + /** + * {@inheritDoc} + */ + @Override + protected String getUrlString(ExecutionEvent event) { + String param = event.getParameter(SEARCH_DOCUMENTATION_PARAMETER); + if (StringUtils.isNotEmpty(param)) { + StringBuilder stringBuilder = new StringBuilder("https://documentation.novatec-gmbh.de/dosearchsite.action?searchQuery.queryString="); + String[] words = StringUtils.split(param); + for (int i = 0; i < words.length; i++) { + stringBuilder.append(words[i]); + if (i < words.length - 1) { + stringBuilder.append('+'); + } + } + stringBuilder.append("&searchQuery.spaceKey=INSPECTIT"); + return stringBuilder.toString(); + } + return super.getUrlString(event); + } + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/OpenViewHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/OpenViewHandler.java new file mode 100644 index 000000000..560744aac --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/OpenViewHandler.java @@ -0,0 +1,59 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.root.FormRootEditor; +import info.novatec.inspectit.rcp.editor.root.RootEditorInput; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * The open view handler which takes care of opening a view by retrieving the + * {@link InputDefinition}. + * + * @see org.eclipse.core.commands.IHandler + * @see org.eclipse.core.commands.AbstractHandler + */ +public class OpenViewHandler extends AbstractHandler { + + /** + * The corresponding command id. + */ + public static final String COMMAND = "info.novatec.inspectit.rcp.commands.openView"; + + /** + * The input definition id to look up. + */ + public static final String INPUT = COMMAND + ".input"; + + /** + * {@inheritDoc} + */ + public Object execute(ExecutionEvent event) throws ExecutionException { + // Get the view + IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindow(event); + IWorkbenchPage page = window.getActivePage(); + + // Get the input definition out of the context + IEvaluationContext context = (IEvaluationContext) event.getApplicationContext(); + InputDefinition inputDefinition = (InputDefinition) context.getVariable(INPUT); + + // open the view if the input definition is set + if (null != inputDefinition) { + RootEditorInput input = new RootEditorInput(inputDefinition); + try { + page.openEditor(input, FormRootEditor.ID); + } catch (PartInitException e) { + throw new ExecutionException("Exception occurred trying to open the editor.", e); + } + } + + return null; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/RefreshEditorHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/RefreshEditorHandler.java new file mode 100644 index 000000000..4c3a9022e --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/RefreshEditorHandler.java @@ -0,0 +1,33 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.editor.ISubView; +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Refresh view handler that refresh the current active sub-view. + * + * @author Ivan Senic + * + */ +public class RefreshEditorHandler extends AbstractHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IEditorPart activeEditor = HandlerUtil.getActiveEditor(event); + if (activeEditor instanceof AbstractRootEditor) { + ISubView subView = ((AbstractRootEditor) activeEditor).getSubView(); + subView.doRefresh(); + } + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/RefreshViewHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/RefreshViewHandler.java new file mode 100644 index 000000000..26615c5bf --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/RefreshViewHandler.java @@ -0,0 +1,32 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.view.IRefreshableView; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Handler for refreshing the {@link IRefreshableView}. + * + * @author Ivan Senic + * + */ +public class RefreshViewHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + IWorkbenchPart workbenchPart = HandlerUtil.getActivePart(event); + if (workbenchPart instanceof IRefreshableView) { + ((IRefreshableView) workbenchPart).refresh(); + } + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/RemoveCmrRepositoryHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/RemoveCmrRepositoryHandler.java new file mode 100644 index 000000000..07e421599 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/RemoveCmrRepositoryHandler.java @@ -0,0 +1,50 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.provider.ICmrRepositoryProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * handler for removing CMR repository. + * + * @author Ivan Senic + * + */ +public class RemoveCmrRepositoryHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + ISelection selection = HandlerUtil.getCurrentSelection(event); + if (selection instanceof StructuredSelection) { + Object selectedObject = ((StructuredSelection) selection).getFirstElement(); + if (selectedObject instanceof ICmrRepositoryProvider) { + CmrRepositoryDefinition cmrRepositoryDefinition = ((ICmrRepositoryProvider) selectedObject).getCmrRepositoryDefinition(); + if (null != cmrRepositoryDefinition) { + boolean isSure = MessageDialog.openConfirm( + null, + "Remove Central Management Repository (CMR)", + "Are you sure that you want to remove the repository " + cmrRepositoryDefinition.getName() + " (" + cmrRepositoryDefinition.getIp() + ":" + + cmrRepositoryDefinition.getPort() + ")?"); + + if (isSure) { + InspectIT.getDefault().getCmrRepositoryManager().removeCmrRepositoryDefinition(cmrRepositoryDefinition); + } + } + } + } + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/RemoveStorageLabelHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/RemoveStorageLabelHandler.java new file mode 100644 index 000000000..15af62cbe --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/RemoveStorageLabelHandler.java @@ -0,0 +1,87 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.provider.IStorageDataProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.view.impl.StorageManagerView; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; + +import java.util.List; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Handler for removing a list of labels from storage. + * + * @author Ivan Senic + * + */ +public class RemoveStorageLabelHandler extends AbstractHandler implements IHandler { + + /** + * Command ID. + */ + public static final String COMMAND = "info.novatec.inspectit.rcp.commands.removeStorageLabel"; + + /** + * Input ID. + */ + public static final String INPUT = COMMAND + ".input"; + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public Object execute(ExecutionEvent event) throws ExecutionException { + // Get the input list out of the context + IEvaluationContext context = (IEvaluationContext) event.getApplicationContext(); + List> inputList = (List>) context.getVariable(INPUT); + + IStorageDataProvider storageProvider = null; + + // try to get it from selection + ISelection selection = HandlerUtil.getCurrentSelection(event); + if (selection instanceof StructuredSelection) { + if (((StructuredSelection) selection).getFirstElement() instanceof IStorageDataProvider) { + storageProvider = (IStorageDataProvider) ((StructuredSelection) selection).getFirstElement(); + } + } + + if (null != storageProvider && null != inputList) { + CmrRepositoryDefinition cmrRepositoryDefinition = storageProvider.getCmrRepositoryDefinition(); + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + try { + StorageData updatedStorageData = cmrRepositoryDefinition.getStorageService().removeLabelsFromStorage(storageProvider.getStorageData(), inputList); + try { + InspectIT.getDefault().getInspectITStorageManager().storageRemotelyUpdated(updatedStorageData); + } catch (Exception e) { + InspectIT.getDefault().createErrorDialog("Error occured trying to save local storage data to disk.", e, -1); + } + IViewPart viewPart = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().findView(StorageManagerView.VIEW_ID); + if (viewPart instanceof StorageManagerView) { + ((StorageManagerView) viewPart).refresh(cmrRepositoryDefinition); + } + } catch (StorageException e) { + return null; + } + } else { + InspectIT.getDefault().createErrorDialog("Labels could not be removed from storage, because the underlying repository is offline.", null, -1); + } + } + + return null; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/RenameEditorHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/RenameEditorHandler.java new file mode 100644 index 000000000..e91d4e600 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/RenameEditorHandler.java @@ -0,0 +1,50 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.editor.root.FormRootEditor; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IInputValidator; +import org.eclipse.jface.dialogs.InputDialog; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Handler for changing the name of the editor. + * + * @author Ivan Senic + * + */ +public class RenameEditorHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + FormRootEditor editor = (FormRootEditor) HandlerUtil.getActiveEditor(event); + Shell shell = HandlerUtil.getActiveShell(event); + IInputValidator inputValidator = new IInputValidator() { + @Override + public String isValid(String newText) { + if (StringUtils.isEmpty(newText)) { + return "Name of the view is required"; + } + return null; + } + }; + InputDialog inputDialog = new InputDialog(shell, "Rename View", "Please enter new name for the active view", editor.getPartName(), inputValidator); + inputDialog.open(); + if (inputDialog.getReturnCode() == Dialog.OK) { + String name = inputDialog.getValue(); + editor.updateEditorName(name); + } + + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/ShowHideColumnsHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/ShowHideColumnsHandler.java new file mode 100644 index 000000000..cb0f2b930 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/ShowHideColumnsHandler.java @@ -0,0 +1,299 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; +import info.novatec.inspectit.rcp.preferences.PreferencesUtils; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.swt.widgets.Item; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TreeColumn; + +/** + * This class is a handler for show/hide of columns, but also a cache for saving the state of the + * columns. + * + * @author Ivan Senic + * + */ +public class ShowHideColumnsHandler extends AbstractHandler { + + /** + * Command ID. + */ + public static final String COMMAND_ID = "info.novatec.inspectit.rcp.commands.showHideColumn"; + + /** + * Column parameter. + */ + public static final String COLUMN_PARAM = "info.novatec.inspectit.rcp.commands.showHideColumn.Column"; + + /** + * Visible parameter. + */ + public static final String VISIBLE_PARAM = "info.novatec.inspectit.rcp.commands.showHideColumn.Visible"; + + /** + * Controller class parameter. + */ + public static final String CONTROLLER_CLASS_PARAM = "info.novatec.inspectit.rcp.commands.showHideColumn.ControllerClass"; + + /** + * Map for saving columns size. + */ + private static Map columnSizeCache; + + /** + * Set for saving columns visibility. All columns that are in this cache are not visible. + */ + private static Set hiddenColumnsCache; + + /** + * Column order map. + */ + private static Map columnOrderCache; + + static { + startUp(); + } + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + // Get the input definition out of the context + IEvaluationContext context = (IEvaluationContext) event.getApplicationContext(); + Item column = (Item) context.getVariable(COLUMN_PARAM); + Boolean visible = (Boolean) context.getVariable(VISIBLE_PARAM); + Class inputControllerClass = (Class) context.getVariable(CONTROLLER_CLASS_PARAM); + + showHideColumn(column, column.getText(), visible.booleanValue(), inputControllerClass); + return null; + } + + /** + * Shows or hides the {@link TreeColumn} or {@link TableColumn}. + * + * @param column + * Column. + * @param columnName + * name of the column. + * @param showColumn + * Should column be shown or not. + * @param controllerClass + * Controller class. + */ + private void showHideColumn(Item column, String columnName, boolean showColumn, Class controllerClass) { + int columnHash = getColumnHash(controllerClass, columnName); + if (showColumn) { + // update cache data + Integer width = columnSizeCache.get(columnHash); + hiddenColumnsCache.remove(columnHash); + + // change appearance + if (width != null) { + setColumnWidth(column, width.intValue()); + } else { + setColumnWidth(column, 100); + } + setColumnResizable(column, true); + } else { + // update cache data + int width = getColumnWidth(column); + hiddenColumnsCache.add(columnHash); + columnSizeCache.put(columnHash, width); + + // change appearance + setColumnWidth(column, 0); + setColumnResizable(column, false); + } + } + + /** + * Sets the with of {@link TreeColumn} or {@link TableColumn}. + * + * @param column + * Column + * @param width + * Width + */ + private void setColumnWidth(Item column, int width) { + if (column instanceof TableColumn) { + ((TableColumn) column).setWidth(width); + } else if (column instanceof TreeColumn) { + ((TreeColumn) column).setWidth(width); + } + } + + /** + * Gets the width of {@link TreeColumn} or {@link TableColumn}. + * + * @param column + * Column. + * @return Width of column, or -1 if provided {@link Item} object is not of type + * {@link TreeColumn} or {@link TableColumn}. + */ + private int getColumnWidth(Item column) { + if (column instanceof TableColumn) { + return ((TableColumn) column).getWidth(); + } else if (column instanceof TreeColumn) { + return ((TreeColumn) column).getWidth(); + } + return -1; + } + + /** + * Sets the {@link TreeColumn} or {@link TableColumn} resizable. + * + * @param column + * Column + * @param resizable + * Resizable or not + */ + private void setColumnResizable(Item column, boolean resizable) { + if (column instanceof TableColumn) { + ((TableColumn) column).setResizable(resizable); + } else if (column instanceof TreeColumn) { + ((TreeColumn) column).setResizable(resizable); + } + } + + /** + * Returns if the cache has any knowledge of the column's width. + * + * @param controllerClass + * {@link info.novatec.inspectit.rcp.editor.table.input.TableInputController} class + * where this column is defined. + * @param columnName + * Column name. + * @return Size of columns width or null if it is unknown. + */ + public static Integer getRememberedColumnWidth(Class controllerClass, String columnName) { + int hash = getColumnHash(controllerClass, columnName); + return columnSizeCache.get(hash); + } + + /** + * Returns if the cache has any knowledge if the column is hidden. + * + * @param controllerClass + * {@link info.novatec.inspectit.rcp.editor.table.input.TableInputController} class + * where this column is defined. + * @param columnName + * Column name. + * @return True if column should be hidden. + */ + public static boolean isColumnHidden(Class controllerClass, String columnName) { + return hiddenColumnsCache.contains(getColumnHash(controllerClass, columnName)); + } + + /** + * Saves the column order for the controller class. + * + * @param controllerClass + * Controller class. + * @param order + * Array that describes the order of the columns. + */ + public static void setColumnOrder(Class controllerClass, int[] order) { + Integer key = Integer.valueOf(controllerClass.getName().hashCode()); + columnOrderCache.remove(key); + columnOrderCache.put(key, order); + } + + /** + * Gets the column order for the controller class. + * + * @param controllerClass + * Controller class. + * @return Array that describes the order of the columns or null if the order was never saved + * for the controller class. + */ + public static int[] getColumnOrder(Class controllerClass) { + Integer key = Integer.valueOf(controllerClass.getName().hashCode()); + return columnOrderCache.get(key); + } + + /** + * Registers new column width to be saved for further use. Only positive column widths will be + * saved. + * + * @param controllerClass + * {@link info.novatec.inspectit.rcp.editor.table.input.TableInputController} class + * where this column is defined. + * @param columnName + * Column name. + * @param width + * New width + */ + public static void registerNewColumnWidth(Class controllerClass, String columnName, int width) { + // only register positive values, thus keep track of the column size + if (width > 0) { + int hash = getColumnHash(controllerClass, columnName); + columnSizeCache.put(hash, width); + } + } + + /** + * Creates hash code for column by its name and the + * {@link info.novatec.inspectit.rcp.editor.table.input.TableInputController} class it is + * located. + * + * @param controllerClass + * {@link info.novatec.inspectit.rcp.editor.table.input.TableInputController} class + * where this column is defined. + * @param columnName + * Column name. + * @return Hash code for caching. + */ + private static int getColumnHash(Class controllerClass, String columnName) { + final int prime = 31; + int result = 0; + result = prime * result + ((controllerClass.getName() == null) ? 0 : controllerClass.getName().hashCode()); + result = prime * result + ((columnName == null) ? 0 : columnName.hashCode()); + return result; + } + + /** + * Loads preferences for columns size/visibility. + */ + private static synchronized void startUp() { + columnSizeCache = new HashMap(); + PreferencesUtils.loadPrimitiveMap(PreferencesConstants.TABLE_COLUMN_SIZE_CACHE, columnSizeCache, Integer.class, Integer.class); + + hiddenColumnsCache = new HashSet(); + PreferencesUtils.loadPrimitiveCollection(PreferencesConstants.HIDDEN_TABLE_COLUMN_CACHE, hiddenColumnsCache, Integer.class); + + columnOrderCache = PreferencesUtils.getObject(PreferencesConstants.TABLE_COLUMN_ORDER_CACHE); + if (null == columnOrderCache) { + columnOrderCache = new HashMap(); + } + + // shut down hook to save the data when closing UI + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + saveChanges(); + } + }); + + } + + /** + * Saves the preferences about columns size/visibility. + */ + private static void saveChanges() { + PreferencesUtils.saveObject(PreferencesConstants.TABLE_COLUMN_SIZE_CACHE, columnSizeCache, false); + PreferencesUtils.saveObject(PreferencesConstants.HIDDEN_TABLE_COLUMN_CACHE, hiddenColumnsCache, false); + PreferencesUtils.saveObject(PreferencesConstants.TABLE_COLUMN_ORDER_CACHE, columnOrderCache, false); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/ShowLabelsManagerHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/ShowLabelsManagerHandler.java new file mode 100644 index 000000000..0b95291eb --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/ShowLabelsManagerHandler.java @@ -0,0 +1,53 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.provider.ICmrRepositoryProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.view.impl.StorageManagerView; +import info.novatec.inspectit.rcp.wizard.ManageLabelWizard; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Show labels manager handler. + * + * @author Ivan Senic + * + */ +public class ShowLabelsManagerHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + ISelection selection = HandlerUtil.getCurrentSelection(event); + if (selection instanceof StructuredSelection) { + Object selectedObject = ((StructuredSelection) selection).getFirstElement(); + if (selectedObject instanceof ICmrRepositoryProvider) { + CmrRepositoryDefinition cmrRepositoryDefinition = ((ICmrRepositoryProvider) selectedObject).getCmrRepositoryDefinition(); + ManageLabelWizard wizard = new ManageLabelWizard(cmrRepositoryDefinition); + WizardDialog dialog = new WizardDialog(HandlerUtil.getActiveShell(event), wizard); + dialog.open(); + + if (wizard.isShouldRefreshStorages()) { + IViewPart viewPart = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().findView(StorageManagerView.VIEW_ID); + if (viewPart instanceof StorageManagerView) { + ((StorageManagerView) viewPart).refresh(cmrRepositoryDefinition); + } + } + } + } + + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/ShowRepositoryHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/ShowRepositoryHandler.java new file mode 100644 index 000000000..fd31c884a --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/ShowRepositoryHandler.java @@ -0,0 +1,71 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; +import info.novatec.inspectit.rcp.view.impl.DataExplorerView; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Opens the {@link RepositoryDefinition} in the {@link DataExplorerView}. + * + * @author Ivan Senic + * + */ +public class ShowRepositoryHandler extends AbstractHandler implements IHandler { + + /** + * The corresponding command id. + */ + public static final String COMMAND = "info.novatec.inspectit.rcp.commands.showRepository"; + + /** + * The repository to look up. + */ + public static final String REPOSITORY_DEFINITION = COMMAND + ".repository"; + + /** + * The repository to look up. + */ + public static final String AGENT = COMMAND + ".agent"; + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + // Get the repository definition and agent out of the context + IEvaluationContext context = (IEvaluationContext) event.getApplicationContext(); + RepositoryDefinition repositoryDefinition = (RepositoryDefinition) context.getVariable(REPOSITORY_DEFINITION); + PlatformIdent platformIdent = (PlatformIdent) context.getVariable(AGENT); + + if (null != repositoryDefinition) { + // find view + IWorkbenchWindow workbenchWindow = HandlerUtil.getActiveWorkbenchWindow(event); + if (null == workbenchWindow) { + workbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + } + + IViewPart viewPart = workbenchWindow.getActivePage().findView(DataExplorerView.VIEW_ID); + if (viewPart == null) { + try { + viewPart = workbenchWindow.getActivePage().showView(DataExplorerView.VIEW_ID); + } catch (PartInitException e) { + return null; + } + } + if (viewPart instanceof DataExplorerView) { + workbenchWindow.getActivePage().activate(viewPart); + ((DataExplorerView) viewPart).showRepository(repositoryDefinition, platformIdent); + } + } + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/ShutdownCmrHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/ShutdownCmrHandler.java new file mode 100644 index 000000000..c855f7cfc --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/ShutdownCmrHandler.java @@ -0,0 +1,116 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.provider.ICmrRepositoryProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.repository.CmrRepositoryManager.UpdateRepositoryJob; + +import java.lang.reflect.InvocationTargetException; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.progress.IProgressService; + +/** + * Handler that performs shutdown and restart of the CMR. + * + * @author Ivan Senic + * + */ +public class ShutdownCmrHandler extends AbstractHandler implements IHandler { + + /** + * Parameter that defines if restart should be executed after along shutdown of CMR. + */ + public static final String SHOULD_RESTART_PARAMETER = "info.novatec.inspectit.rcp.commands.shutdown.shouldRestart"; + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + String param = event.getParameter(SHOULD_RESTART_PARAMETER); + ISelection selection = HandlerUtil.getCurrentSelection(event); + if (StringUtils.isNotEmpty(param) && selection instanceof StructuredSelection) { + final boolean shouldRestart = Boolean.parseBoolean(param); + Object selectedObject = ((StructuredSelection) selection).getFirstElement(); + if (selectedObject instanceof ICmrRepositoryProvider) { + final CmrRepositoryDefinition cmrRepositoryDefinition = ((ICmrRepositoryProvider) selectedObject).getCmrRepositoryDefinition(); + String cmrName = "'" + cmrRepositoryDefinition.getName() + "' (" + cmrRepositoryDefinition.getIp() + ":" + cmrRepositoryDefinition.getPort() + ")"; + boolean confirm; + if (shouldRestart) { + confirm = MessageDialog.openConfirm(HandlerUtil.getActiveShell(event), "Restart CMR", "Are you sure you want to restart the CMR " + cmrName + "?"); + } else { + confirm = MessageDialog.openConfirm(HandlerUtil.getActiveShell(event), "Shutdown CMR", "Are you sure you want to shutdown the CMR " + cmrName + "?"); + } + if (confirm) { + IProgressService progressService = PlatformUI.getWorkbench().getProgressService(); + try { + progressService.busyCursorWhile(new IRunnableWithProgress() { + @Override + public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + if (shouldRestart) { + monitor.beginTask("Restarting the CMR", IProgressMonitor.UNKNOWN); + cmrRepositoryDefinition.getCmrManagementService().restart(); + } else { + monitor.beginTask("Shutting down the CMR", IProgressMonitor.UNKNOWN); + cmrRepositoryDefinition.getCmrManagementService().shutdown(); + } + + cmrRepositoryDefinition.changeOnlineStatus(OnlineStatus.CHECKING); + cmrRepositoryDefinition.changeOnlineStatus(OnlineStatus.OFFLINE); + + // we first sleep so that CMR can shutdown + // this will ensure that this method does not return to fast and CMR + // is still online + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + Thread.interrupted(); + } + + // then if we are restarting wait until we are up + if (shouldRestart) { + monitor.beginTask("Waiting for the CMR to be online again", IProgressMonitor.UNKNOWN); + while (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.ONLINE) { + // first let the CMR restart, we can not invoke the service + // right away cause it can end up in endless service request + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + Thread.interrupted(); + } + + // we force status update and wait until job has finished + UpdateRepositoryJob updateRepositoryJob = InspectIT.getDefault().getCmrRepositoryManager().forceCmrRepositoryOnlineStatusUpdate(cmrRepositoryDefinition); + updateRepositoryJob.join(); + + if (monitor.isCanceled()) { + return; + } + } + } + + monitor.done(); + } + }); + } catch (InvocationTargetException | InterruptedException e) { + throw new ExecutionException("Exception occurred during execution of shutdown/restart handler", e); + } + } + } + } + return null; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/StartRecordingHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/StartRecordingHandler.java new file mode 100644 index 000000000..ae703a929 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/StartRecordingHandler.java @@ -0,0 +1,124 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.communication.data.cmr.CmrStatusData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.provider.ICmrRepositoryAndAgentProvider; +import info.novatec.inspectit.rcp.provider.ICmrRepositoryProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.view.impl.RepositoryManagerView; +import info.novatec.inspectit.rcp.view.impl.StorageManagerView; +import info.novatec.inspectit.rcp.wizard.StartRecordingWizard; +import info.novatec.inspectit.storage.recording.RecordingProperties; + +import java.util.Collection; +import java.util.Collections; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.progress.IProgressConstants; + +/** + * Starts recording. + * + * @author Ivan Senic + * + */ +public class StartRecordingHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + // try to get the CMR where recording should start. + CmrRepositoryDefinition cmrRepositoryDefinition = null; + Collection autoSelectedAgents = Collections.emptyList(); + ISelection selection = HandlerUtil.getCurrentSelection(event); + if (selection instanceof StructuredSelection) { + Object selectedObject = ((StructuredSelection) selection).getFirstElement(); + if (selectedObject instanceof ICmrRepositoryProvider) { + cmrRepositoryDefinition = ((ICmrRepositoryProvider) selectedObject).getCmrRepositoryDefinition(); + } else if (selectedObject instanceof ICmrRepositoryAndAgentProvider) { + cmrRepositoryDefinition = ((ICmrRepositoryAndAgentProvider) selectedObject).getCmrRepositoryDefinition(); + autoSelectedAgents = Collections.singletonList(((ICmrRepositoryAndAgentProvider) selectedObject).getPlatformIdent()); + } + } + + // check if the writing state is OK + if (null != cmrRepositoryDefinition) { + try { + CmrStatusData cmrStatusData = cmrRepositoryDefinition.getCmrManagementService().getCmrStatusData(); + if (cmrStatusData.isWarnSpaceLeftActive()) { + String leftSpace = NumberFormatter.humanReadableByteCount(cmrStatusData.getStorageDataSpaceLeft()); + if (!MessageDialog.openQuestion(HandlerUtil.getActiveShell(event), "Confirm", "For selected CMR there is an active warning about insufficient storage space left. Only " + + leftSpace + " are left on the target server, are you sure you want to continue?")) { + return null; + } + } + } catch (Exception e) { // NOPMD NOCHK + // ignore because if we can not get the info. we will still respond to user action + } + } + + // open wizard + StartRecordingWizard startRecordingWizard = new StartRecordingWizard(cmrRepositoryDefinition, autoSelectedAgents); + WizardDialog wizardDialog = new WizardDialog(HandlerUtil.getActiveShell(event), startRecordingWizard); + wizardDialog.open(); + + // if recording has been started refresh the repository and storage manager view + if (wizardDialog.getReturnCode() == WizardDialog.OK) { + final IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + IViewPart repositoryManagerView = activePage.findView(RepositoryManagerView.VIEW_ID); + if (repositoryManagerView instanceof RepositoryManagerView) { + ((RepositoryManagerView) repositoryManagerView).refresh(); + } + IViewPart storageManagerView = activePage.findView(StorageManagerView.VIEW_ID); + if (storageManagerView instanceof StorageManagerView) { + if (null != cmrRepositoryDefinition) { + ((StorageManagerView) storageManagerView).refresh(cmrRepositoryDefinition); + } else { + ((StorageManagerView) storageManagerView).refresh(); + } + } + + // auto-refresh on recording stop if there is recording duration specified + RecordingProperties recordingProperties = startRecordingWizard.getRecordingProperties(); + if (null != recordingProperties && recordingProperties.getRecordDuration() > 0) { + Job refreshStorageManagerJob = new Job("Recording Auto-Stop Updates") { + @Override + protected IStatus run(IProgressMonitor monitor) { + IViewPart storageManagerView = activePage.findView(StorageManagerView.VIEW_ID); + if (storageManagerView instanceof StorageManagerView) { + ((StorageManagerView) storageManagerView).refresh(); + } + return Status.OK_STATUS; + } + }; + refreshStorageManagerJob.setUser(false); + refreshStorageManagerJob.setProperty(IProgressConstants.ICON_PROPERTY, InspectIT.getDefault().getImage(InspectITImages.IMG_RECORD_STOP)); + // add 5 seconds to be sure all is done + long delay = 5000 + recordingProperties.getRecordDuration() + recordingProperties.getStartDelay(); + refreshStorageManagerJob.schedule(delay); + } + } + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/StopRecordingHanlder.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/StopRecordingHanlder.java new file mode 100644 index 000000000..9736e2efc --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/StopRecordingHanlder.java @@ -0,0 +1,104 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.provider.ICmrRepositoryAndAgentProvider; +import info.novatec.inspectit.rcp.provider.ICmrRepositoryProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.view.impl.RepositoryManagerView; +import info.novatec.inspectit.rcp.view.impl.StorageManagerView; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.recording.RecordingState; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.progress.IProgressConstants; + +/** + * Stops recording. + * + * @author Ivan Senic + * + */ +public class StopRecordingHanlder extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(final ExecutionEvent event) throws ExecutionException { + CmrRepositoryDefinition cmrRepositoryDefinition = null; + ISelection selection = HandlerUtil.getCurrentSelection(event); + if (selection instanceof StructuredSelection) { + Object selectedObject = ((StructuredSelection) selection).getFirstElement(); + if (selectedObject instanceof ICmrRepositoryProvider) { + cmrRepositoryDefinition = ((ICmrRepositoryProvider) selectedObject).getCmrRepositoryDefinition(); + } else if (((StructuredSelection) selection).getFirstElement() instanceof ICmrRepositoryAndAgentProvider) { + cmrRepositoryDefinition = ((ICmrRepositoryAndAgentProvider) selectedObject).getCmrRepositoryDefinition(); + } + } + if (null != cmrRepositoryDefinition) { + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + boolean canStop = cmrRepositoryDefinition.getStorageService().getRecordingState() != RecordingState.OFF; + if (canStop) { + try { + final CmrRepositoryDefinition finalCmrRepositoryDefinition = cmrRepositoryDefinition; + Job stopRecordingJob = new Job("Stop Recording") { + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + finalCmrRepositoryDefinition.getStorageService().stopRecording(); + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + IViewPart repositoryManagerView = activePage.findView(RepositoryManagerView.VIEW_ID); + if (repositoryManagerView instanceof RepositoryManagerView) { + ((RepositoryManagerView) repositoryManagerView).refresh(); + } + IViewPart storageManagerView = activePage.findView(StorageManagerView.VIEW_ID); + if (storageManagerView instanceof StorageManagerView) { + ((StorageManagerView) storageManagerView).refresh(finalCmrRepositoryDefinition); + } + } + }); + } catch (final StorageException e) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + InspectIT.getDefault().createErrorDialog("Stopping the recording failed", e, -1); + } + }); + } + return Status.OK_STATUS; + } + }; + stopRecordingJob.setUser(true); + stopRecordingJob.setProperty(IProgressConstants.ICON_PROPERTY, InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_RECORD_STOP)); + stopRecordingJob.schedule(); + } catch (Exception e) { + InspectIT.getDefault().createErrorDialog("Execution Error", e, -1); + } + } + } else { + InspectIT.getDefault().createErrorDialog("Recording can not be stopped, because the repository is currently offline.", null, -1); + } + } + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/TableCopyHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/TableCopyHandler.java new file mode 100644 index 000000000..a58f0fcff --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/TableCopyHandler.java @@ -0,0 +1,62 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.editor.table.TableSubView; + +import java.util.Iterator; +import java.util.List; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * The handler to execute a copy command on our table sub views. + * + * @author Patrice Bouillet + * + */ +public class TableCopyHandler extends AbstractHandler { + + /** + * {@inheritDoc} + */ + public Object execute(ExecutionEvent event) throws ExecutionException { + IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelection(event); + AbstractRootEditor rootEditor = (AbstractRootEditor) HandlerUtil.getActiveEditor(event); + TableSubView subView = (TableSubView) rootEditor.getActiveSubView(); + + List visibleColumnOrder = subView.getColumnOrder(); + StringBuilder sb = new StringBuilder(); + + // columns first + List columnNames = subView.getColumnNames(); + for (Integer index : visibleColumnOrder) { + sb.append(columnNames.get(index.intValue())); + sb.append('\t'); + } + sb.append(System.getProperty("line.separator")); + + // then each object + for (Iterator iterator = selection.iterator(); iterator.hasNext();) { + Object object = iterator.next(); + List columnValues = subView.getTableInputController().getColumnValues(object); + for (Integer index : visibleColumnOrder) { + sb.append(columnValues.get(index.intValue())); + sb.append('\t'); + } + sb.append(System.getProperty("line.separator")); + } + + TextTransfer textTransfer = TextTransfer.getInstance(); + Clipboard cb = new Clipboard(HandlerUtil.getActiveShell(event).getDisplay()); + cb.setContents(new Object[] { sb.toString() }, new Transfer[] { textTransfer }); + + return null; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/TreeCollapseHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/TreeCollapseHandler.java new file mode 100644 index 000000000..59d1a2f67 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/TreeCollapseHandler.java @@ -0,0 +1,52 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.editor.tree.TreeSubView; + +import java.util.Iterator; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Collapse handler for trees. + * + * @author Ivan Senic + * + */ +public class TreeCollapseHandler extends AbstractHandler implements IHandler { + + /** + * Parameter that defines if collapse is performed on all elements or just the selected ones. + */ + public static final String IS_COLLAPSE_ALL_PARAMETER = "info.novatec.inspectit.rcp.commands.collapse.isCollapseAll"; + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + String param = event.getParameter(IS_COLLAPSE_ALL_PARAMETER); + if (StringUtils.isNotEmpty(param)) { + boolean isCollapseAll = Boolean.parseBoolean(param); + AbstractRootEditor rootEditor = (AbstractRootEditor) HandlerUtil.getActiveEditor(event); + TreeSubView treeSubView = (TreeSubView) rootEditor.getActiveSubView(); + if (isCollapseAll) { + treeSubView.getTreeViewer().collapseAll(); + } else { + IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelection(event); + for (Iterator iterator = selection.iterator(); iterator.hasNext();) { + Object object = iterator.next(); + treeSubView.getTreeViewer().collapseToLevel(object, TreeViewer.ALL_LEVELS); + } + } + } + return null; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/TreeCopyHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/TreeCopyHandler.java new file mode 100644 index 000000000..c4953227a --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/TreeCopyHandler.java @@ -0,0 +1,62 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.editor.tree.TreeSubView; + +import java.util.Iterator; +import java.util.List; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * The handler to execute a copy command on our tree sub views. + * + * @author Patrice Bouillet + * + */ +public class TreeCopyHandler extends AbstractHandler { + + /** + * {@inheritDoc} + */ + public Object execute(ExecutionEvent event) throws ExecutionException { + IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelection(event); + AbstractRootEditor rootEditor = (AbstractRootEditor) HandlerUtil.getActiveEditor(event); + TreeSubView subView = (TreeSubView) rootEditor.getActiveSubView(); + + List visibleColumnOrder = subView.getColumnOrder(); + StringBuilder sb = new StringBuilder(); + + // columns first + List columnNames = subView.getColumnNames(); + for (Integer index : visibleColumnOrder) { + sb.append(columnNames.get(index.intValue())); + sb.append('\t'); + } + sb.append(System.getProperty("line.separator")); + + // then each object + for (Iterator iterator = selection.iterator(); iterator.hasNext();) { + Object object = iterator.next(); + List columnValues = subView.getTreeInputController().getColumnValues(object); + for (Integer index : visibleColumnOrder) { + sb.append(columnValues.get(index.intValue())); + sb.append('\t'); + } + sb.append(System.getProperty("line.separator")); + } + + TextTransfer textTransfer = TextTransfer.getInstance(); + Clipboard cb = new Clipboard(HandlerUtil.getActiveShell(event).getDisplay()); + cb.setContents(new Object[] { sb.toString() }, new Transfer[] { textTransfer }); + + return null; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/TreeExpandHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/TreeExpandHandler.java new file mode 100644 index 000000000..152148a13 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/TreeExpandHandler.java @@ -0,0 +1,53 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.editor.root.AbstractRootEditor; +import info.novatec.inspectit.rcp.editor.tree.TreeSubView; + +import java.util.Iterator; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Expand handler for trees. + * + * @author Ivan Senic + * + */ +public class TreeExpandHandler extends AbstractHandler implements IHandler { + + /** + * Parameter that defines if expand is performed on all elements or just the selected ones. + */ + public static final String IS_EXPAND_ALL_PARAMETER = "info.novatec.inspectit.rcp.commands.expand.isExpandAll"; + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + String param = event.getParameter(IS_EXPAND_ALL_PARAMETER); + if (StringUtils.isNotEmpty(param)) { + boolean isExpandAll = Boolean.parseBoolean(param); + AbstractRootEditor rootEditor = (AbstractRootEditor) HandlerUtil.getActiveEditor(event); + TreeSubView treeSubView = (TreeSubView) rootEditor.getActiveSubView(); + if (isExpandAll) { + treeSubView.getTreeViewer().expandAll(); + } else { + IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelection(event); + for (Iterator iterator = selection.iterator(); iterator.hasNext();) { + Object object = iterator.next(); + treeSubView.getTreeViewer().expandToLevel(object, TreeViewer.ALL_LEVELS); + } + } + } + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/handlers/UploadStorageHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/handlers/UploadStorageHandler.java new file mode 100644 index 000000000..2cb97ad96 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/handlers/UploadStorageHandler.java @@ -0,0 +1,41 @@ +package info.novatec.inspectit.rcp.handlers; + +import info.novatec.inspectit.rcp.provider.ILocalStorageDataProvider; +import info.novatec.inspectit.rcp.wizard.UploadStorageWizard; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.ui.handlers.HandlerUtil; + +/** + * Handler for starting the {@link UploadStorageWizard} upon the correct selection. + * + * @author Ivan Senic + * + */ +public class UploadStorageHandler extends AbstractHandler implements IHandler { + + /** + * {@inheritDoc} + */ + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + ISelection selection = HandlerUtil.getCurrentSelection(event); + if (selection instanceof StructuredSelection) { + Object selected = ((StructuredSelection) selection).getFirstElement(); + if (selected instanceof ILocalStorageDataProvider) { + ILocalStorageDataProvider localStorageDataProvider = (ILocalStorageDataProvider) selected; + WizardDialog wizardDialog = new WizardDialog(HandlerUtil.getActiveShell(event), new UploadStorageWizard(localStorageDataProvider)); + wizardDialog.open(); + } + + } + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/log/LogListener.java b/inspectIT/src/info/novatec/inspectit/rcp/log/LogListener.java new file mode 100644 index 000000000..dfda2c478 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/log/LogListener.java @@ -0,0 +1,43 @@ +package info.novatec.inspectit.rcp.log; + +import org.eclipse.core.runtime.ILogListener; +import org.eclipse.core.runtime.IStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of the {@link ILogListener} to correctly hook into the internal logging mechanism. + * + * @author Ivan Senic + * + */ +public class LogListener implements ILogListener { + + /** + * Logger. + */ + private static final Logger LOG = LoggerFactory.getLogger(LogListener.class); + + /** + * {@inheritDoc} + */ + @Override + public void logging(IStatus status, String plugin) { + if (status.getSeverity() == IStatus.INFO) { + LOG.info(status.getMessage()); + } else if (status.getSeverity() == IStatus.WARNING) { + if (null == status.getException()) { + LOG.warn(status.getMessage()); + } else { + LOG.warn(status.getMessage(), status.getException()); + } + } else if (status.getSeverity() == IStatus.ERROR) { + if (null == status.getException()) { + LOG.error(status.getMessage()); + } else { + LOG.error(status.getMessage(), status.getException()); + } + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/menu/SearchDocumentationContributionItem.java b/inspectIT/src/info/novatec/inspectit/rcp/menu/SearchDocumentationContributionItem.java new file mode 100644 index 000000000..fb30c94bb --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/menu/SearchDocumentationContributionItem.java @@ -0,0 +1,142 @@ +package info.novatec.inspectit.rcp.menu; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.handlers.OpenUrlHandler.SearchDocumentationHandler; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.menus.WorkbenchWindowControlContribution; +import org.eclipse.ui.services.IEvaluationService; + +/** + * The documentation search contribution item displayed in the main toolbar. + * + * @author Ivan Senic + * + */ +public class SearchDocumentationContributionItem extends WorkbenchWindowControlContribution { + + /** + * Default text. + */ + private static final String DEFAULT_TEXT = "Wiki search"; + + /** + * Text box for documentation search. + */ + private Text searchText; + + /** + * Default constructor. + */ + public SearchDocumentationContributionItem() { + } + + /** + * Secondary constructor. + * + * @param id + * Id of contribution item + */ + public SearchDocumentationContributionItem(String id) { + super(id); + } + + /** + * {@inheritDoc} + */ + @Override + protected Control createControl(Composite parent) { + Composite main = new Composite(parent, SWT.NONE); + GridLayout gl = new GridLayout(1, false); + gl.marginHeight = 0; + main.setLayout(gl); + + searchText = new Text(main, SWT.SINGLE | SWT.BORDER | SWT.ICON_SEARCH | SWT.SEARCH); + setDefaultText(); + GridData gridData = new GridData(SWT.CENTER, SWT.CENTER, false, false); + gridData.widthHint = 150; + searchText.setLayoutData(gridData); + searchText.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + if (Objects.equals(searchText.getText(), DEFAULT_TEXT)) { + setEmptyText(); + } + } + + @Override + public void focusLost(FocusEvent e) { + if (Objects.equals(searchText.getText(), "")) { + setDefaultText(); + } + } + }); + searchText.addTraverseListener(new TraverseListener() { + @Override + public void keyTraversed(TraverseEvent e) { + if (e.detail == SWT.TRAVERSE_ESCAPE) { + setDefaultText(); + searchText.getParent().forceFocus(); + } else if (e.detail == SWT.TRAVERSE_RETURN) { + executeSearch(); + } + } + }); + + return main; + } + + /** + * Sets default text in gray color. + */ + private void setDefaultText() { + searchText.setText(DEFAULT_TEXT); + searchText.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_GRAY)); + } + + /** + * Empties the text box and set font color to black. + */ + private void setEmptyText() { + searchText.setText(""); + searchText.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); + } + + /** + * Executes the search. + */ + private void executeSearch() { + String searchString = searchText.getText(); + if (StringUtils.isNotBlank(searchString)) { + ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + IEvaluationService evaluationService = (IEvaluationService) PlatformUI.getWorkbench().getService(IEvaluationService.class); + try { + Command searchCommand = commandService.getCommand("info.novatec.inspectit.rcp.commands.searchDocumentation"); + Map params = new HashMap(); + params.put(SearchDocumentationHandler.SEARCH_DOCUMENTATION_PARAMETER, searchString); + searchCommand.executeWithChecks(new ExecutionEvent(searchCommand, params, searchText, evaluationService.getCurrentState())); + } catch (Exception e) { + InspectIT.getDefault().createErrorDialog("There was an exception executing the wiki search.", e, -1); + } + } + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/menu/ShowHideMenuManager.java b/inspectIT/src/info/novatec/inspectit/rcp/menu/ShowHideMenuManager.java new file mode 100644 index 000000000..053bbab9b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/menu/ShowHideMenuManager.java @@ -0,0 +1,182 @@ +package info.novatec.inspectit.rcp.menu; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.handlers.ShowHideColumnsHandler; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.ColumnViewer; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Item; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TreeColumn; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.handlers.IHandlerService; + +/** + * Menu manager for displaying the show/hide columns group. + * + * @author Ivan Senic + * + */ +public class ShowHideMenuManager extends MenuManager implements IMenuListener { + + /** + * Viewer to display menu for. + */ + private ColumnViewer columnViewer; + + /** + * Input controller class. + */ + private Class inputControllerClass; + + /** + * Default constructor. + * + * @param columnViewer + * Viewer to display menu for. + * @param inputControllerClass + * Input controller class. + */ + public ShowHideMenuManager(ColumnViewer columnViewer, Class inputControllerClass) { + Assert.isNotNull(columnViewer); + Assert.isNotNull(inputControllerClass); + + this.columnViewer = columnViewer; + this.inputControllerClass = inputControllerClass; + + this.addMenuListener(this); + for (IAction actionItem : getActionItems()) { + this.add(actionItem); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void menuAboutToShow(IMenuManager manager) { + this.removeAll(); + for (IAction actionItem : getActionItems()) { + this.add(actionItem); + } + } + + /** + * Returns all contribution items. + * + * @return Returns all contribution items. + */ + private IAction[] getActionItems() { + List items = new ArrayList(); + if (columnViewer instanceof TableViewer) { + TableColumn[] columns = ((TableViewer) columnViewer).getTable().getColumns(); + for (TableColumn column : columns) { + items.add(new ShowHideColumnAction(column)); + } + } else if (columnViewer instanceof TreeViewer) { + TreeColumn[] columns = ((TreeViewer) columnViewer).getTree().getColumns(); + for (TreeColumn column : columns) { + items.add(new ShowHideColumnAction(column)); + } + } + return items.toArray(new IAction[items.size()]); + } + + /** + * Action for showing ot hiding one column. + * + * @author Ivan Senic + * + */ + private class ShowHideColumnAction extends Action { + + /** + * Column for the action. + */ + private Item column; + + /** + * Should the column be visible after action execution. + */ + private boolean visible; + + /** + * Default constructor. + * + * @param column + * Column for the action. + */ + public ShowHideColumnAction(Item column) { + Assert.isNotNull(column); + this.column = column; + + String tooltip; + ImageDescriptor icon = null; + int width = 0; + if (column instanceof TableColumn) { + width = ((TableColumn) column).getWidth(); + } else if (column instanceof TreeColumn) { + width = ((TreeColumn) column).getWidth(); + } else { + RuntimeException exception = new RuntimeException("Unsupported item provided during dynamic columns menu creation. Item class is " + column.getClass().getName() + "."); + InspectIT.getDefault().createErrorDialog("Error creating dynamic column menu", exception, -1); + throw exception; + } + + if (width > 0) { + visible = false; + tooltip = "Hide column"; + icon = InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_CHECKMARK); + } else { + visible = true; + tooltip = "Show column"; + } + + this.setText(column.getText()); + this.setImageDescriptor(icon); + this.setToolTipText(tooltip); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + + IHandlerService handlerService = (IHandlerService) window.getService(IHandlerService.class); + ICommandService commandService = (ICommandService) window.getService(ICommandService.class); + + Command command = commandService.getCommand(ShowHideColumnsHandler.COMMAND_ID); + ExecutionEvent executionEvent = handlerService.createExecutionEvent(command, new Event()); + IEvaluationContext context = (IEvaluationContext) executionEvent.getApplicationContext(); + context.addVariable(ShowHideColumnsHandler.COLUMN_PARAM, column); + context.addVariable(ShowHideColumnsHandler.VISIBLE_PARAM, visible); + context.addVariable(ShowHideColumnsHandler.CONTROLLER_CLASS_PARAM, inputControllerClass); + + try { + command.executeWithChecks(executionEvent); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/AccessFlag.java b/inspectIT/src/info/novatec/inspectit/rcp/model/AccessFlag.java new file mode 100644 index 000000000..cf0ade852 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/AccessFlag.java @@ -0,0 +1,42 @@ +package info.novatec.inspectit.rcp.model; + +/** + * A support class providing static methods and constants for access modifiers such as public, + * private, ... + *

+ * IMPORTANT: The class code is copied/taken/based from javassist. Original author is Shigeru + * Chiba. License info can be found here. + * + */ +public final class AccessFlag { + public static final int PUBLIC = 0x0001; // NOCHK + public static final int PRIVATE = 0x0002; // NOCHK + public static final int PROTECTED = 0x0004; // NOCHK + public static final int STATIC = 0x0008; // NOCHK + public static final int FINAL = 0x0010; // NOCHK + public static final int SYNCHRONIZED = 0x0020; // NOCHK + public static final int VOLATILE = 0x0040; // NOCHK + public static final int BRIDGE = 0x0040; // for method_info NOCHK + public static final int TRANSIENT = 0x0080; // NOCHK + public static final int VARARGS = 0x0080; // for method_info NOCHK + public static final int NATIVE = 0x0100; // NOCHK + public static final int INTERFACE = 0x0200; // NOCHK + public static final int ABSTRACT = 0x0400; // NOCHK + public static final int STRICT = 0x0800; // NOCHK + public static final int SYNTHETIC = 0x1000; // NOCHK + public static final int ANNOTATION = 0x2000; // NOCHK + public static final int ENUM = 0x4000; // NOCHK + + public static final int SUPER = 0x0020; // NOCHK + + /** + * private constructor. + */ + private AccessFlag() { + } + + // Note: 0x0020 is assigned to both ACC_SUPER and ACC_SYNCHRONIZED + // although java.lang.reflect.Modifier does not recognize ACC_SUPER. +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/AgentFolderFactory.java b/inspectIT/src/info/novatec/inspectit/rcp/model/AgentFolderFactory.java new file mode 100644 index 000000000..985a78adb --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/AgentFolderFactory.java @@ -0,0 +1,189 @@ +package info.novatec.inspectit.rcp.model; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.communication.data.cmr.AgentStatusData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.lang.StringUtils; + +/** + * Factory for creating the input data representing the agents in the tree folder structure. + * + * @author Ivan Senic + * + */ +public final class AgentFolderFactory { + + /** + * Folder split regex. + */ + private static final String FOLDER_SPLIT_REGEX = "/"; + + /** + * Private constructor. + */ + private AgentFolderFactory() { + } + + /** + * Returns the list of components representing the input tree structure of agents divided if + * needed to folders. + * + * @param platformIdentMap + * {@link Map} of {@link PlatformIdent}s and their statuses. + * @param cmrRepositoryDefinition + * Repository agents belong to. + * @return List of components. + */ + public static List getAgentFolderTree(Map platformIdentMap, CmrRepositoryDefinition cmrRepositoryDefinition) { + Composite dummy = new Composite(); + for (Entry entry : platformIdentMap.entrySet()) { + PlatformIdent platformIdent = entry.getKey(); + AgentStatusData agentStatusData = entry.getValue(); + + addToFolder(dummy, 0, platformIdent, agentStatusData, cmrRepositoryDefinition); + } + return dummy.getChildren(); + } + + /** + * Adds the platform to the folder on the given level. This method is recursive. If agent name + * is not fitting the level, it will be added to the given composite. If it fitting for the + * level, proper search will be done for sub-composite to insert the platform. + * + * @param folder + * Top composite. + * @param level + * Wanted level. + * @param platformIdent + * {@link PlatformIdent}. + * @param agentStatusData + * {@link AgentStatusData}. + * @param cmrRepositoryDefinition + * Repository agent is belonging to. + */ + private static void addToFolder(Composite folder, int level, PlatformIdent platformIdent, AgentStatusData agentStatusData, CmrRepositoryDefinition cmrRepositoryDefinition) { + if (!accessibleForLevel(platformIdent.getAgentName(), level)) { + // if name is not matching the level just add th leaf + AgentLeaf agentLeaf = new AgentLeaf(platformIdent, agentStatusData, cmrRepositoryDefinition, level != 0); + folder.addChild(agentLeaf); + } else { + // search for proper folder + boolean folderExisting = false; + String agentLevelName = getFolderNameFromAgent(platformIdent.getAgentName(), level); + for (Component child : folder.getChildren()) { + if (child instanceof Composite && ObjectUtils.equals(child.getName(), agentLevelName)) { + addToFolder((Composite) child, level + 1, platformIdent, agentStatusData, cmrRepositoryDefinition); + folderExisting = true; + } + } + + // if not found create new one + if (!folderExisting) { + Composite newFolder = createFolder(agentLevelName); + addToFolder(newFolder, level + 1, platformIdent, agentStatusData, cmrRepositoryDefinition); + folder.addChild(newFolder); + } + } + } + + /** + * Creates new folder with given name. + * + * @param levelName + * Name. + * @return {@link Composite}. + */ + private static Composite createFolder(String levelName) { + Composite composite = new Composite(); + composite.setName(levelName); + composite.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_FOLDER)); + return composite; + } + + /** + * Does agent name applies that agent should be put in the folder. + * + * @param agentName + * Name of the agent + * @return True if name of the agent has correctly specified folder structure at least for the 0 + * level (root folder). + */ + public static boolean accessibleForFolder(String agentName) { + return accessibleForLevel(agentName, 0); + } + + /** + * Returns what should be the name of the agent if the agent is located in a folder. + * + * @param agentName + * Name of the agent + * @return Display name. Result is the string between last found {@value #FOLDER_SPLIT_REGEX} + * and end of name. + */ + public static String getAgentDisplayNameInFolder(String agentName) { + String[] splitted = getSplittedAgentName(agentName); + return splitted[splitted.length - 1]; + } + + /** + * Checks if the name of the agent is accessible for the given level of folder. + * + * @param agentName + * Name of the agent + * @param level + * Level + * @return True if name of the agent has correctly specified folder structure for given level. + */ + private static boolean accessibleForLevel(String agentName, int level) { + String[] splitted = getSplittedAgentName(agentName); + if (splitted.length > level + 1) { + for (String string : splitted) { + if (StringUtils.isEmpty(string)) { + return false; + } + } + return true; + } else { + return false; + } + } + + /** + * Returns folder name by extracting it from the agent name. + * + * @param agentName + * Name of the agent + * @param level + * Level + * @return Name or null if the agent name is not accessible for the given level. + */ + private static String getFolderNameFromAgent(String agentName, int level) { + String[] splitted = getSplittedAgentName(agentName); + if (splitted.length > level) { + return splitted[level]; + } else { + return null; + } + } + + /** + * @param agentName + * Agent name. + * @return Splits the agent name to several strings that represent the folder names. + */ + private static String[] getSplittedAgentName(String agentName) { + if (null != agentName) { + return agentName.split(FOLDER_SPLIT_REGEX); + } else { + return new String[0]; + } + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/AgentLeaf.java b/inspectIT/src/info/novatec/inspectit/rcp/model/AgentLeaf.java new file mode 100644 index 000000000..f7a871923 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/AgentLeaf.java @@ -0,0 +1,149 @@ +package info.novatec.inspectit.rcp.model; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.communication.data.cmr.AgentStatusData; +import info.novatec.inspectit.rcp.provider.ICmrRepositoryAndAgentProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import com.google.common.base.Objects; + +/** + * Agent leaf for the tree in the Repository Manager. + * + * @author Ivan Senic + * + */ +public class AgentLeaf extends Leaf implements ICmrRepositoryAndAgentProvider { + + /** + * Agent. + */ + private PlatformIdent platformIdent; + + /** + * {@link AgentStatusData}. + */ + private AgentStatusData agentStatusData; + + /** + * {@link CmrRepositoryDefinition}. + */ + private final CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * Is this leaf part of the folder. + */ + private final boolean inFolder; + + /** + * Default constructor. + * + * @param platformIdent + * Agent to display in leaf. + * @param agentStatusData + * {@link AgentStatusData} + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} + * @param inFolder + * Is this leaf part of the folder. + */ + public AgentLeaf(PlatformIdent platformIdent, AgentStatusData agentStatusData, CmrRepositoryDefinition cmrRepositoryDefinition, boolean inFolder) { + this.platformIdent = platformIdent; + this.agentStatusData = agentStatusData; + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + this.inFolder = inFolder; + } + + /** + * Gets {@link #platformIdent}. + * + * @return {@link #platformIdent} + */ + public PlatformIdent getPlatformIdent() { + return platformIdent; + } + + /** + * Sets {@link #platformIdent}. + * + * @param platformIdent + * New value for {@link #platformIdent} + */ + public void setPlatformIdent(PlatformIdent platformIdent) { + this.platformIdent = platformIdent; + } + + /** + * Gets {@link #agentStatusData}. + * + * @return {@link #agentStatusData} + */ + public AgentStatusData getAgentStatusData() { + return agentStatusData; + } + + /** + * Sets {@link #agentStatusData}. + * + * @param agentStatusData + * New value for {@link #agentStatusData} + */ + public void setAgentStatusData(AgentStatusData agentStatusData) { + this.agentStatusData = agentStatusData; + } + + /** + * {@inheritDoc} + */ + @Override + public String getName() { + return platformIdent.getAgentName(); + } + + /** + * Gets {@link #inFolder}. + * + * @return {@link #inFolder} + */ + public boolean isInFolder() { + return inFolder; + } + + /** + * Gets {@link #cmrRepositoryDefinition}. + * + * @return {@link #cmrRepositoryDefinition} + */ + public CmrRepositoryDefinition getCmrRepositoryDefinition() { + return cmrRepositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), platformIdent, cmrRepositoryDefinition); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + if (!super.equals(object)) { + return false; + } + AgentLeaf that = (AgentLeaf) object; + return Objects.equal(this.platformIdent, that.platformIdent) && Objects.equal(this.cmrRepositoryDefinition, that.cmrRepositoryDefinition); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/Component.java b/inspectIT/src/info/novatec/inspectit/rcp/model/Component.java new file mode 100644 index 000000000..9fc29bf56 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/Component.java @@ -0,0 +1,188 @@ +package info.novatec.inspectit.rcp.model; + +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; + +import org.eclipse.swt.graphics.Image; + +import com.google.common.base.Objects; + +/** + * A component can be used in any tree based views. + * + * @author Patrice Bouillet + * + */ +public abstract class Component { + + /** + * The name of the component. + */ + private String name; + + /** + * The tooltip of the component, if there is any. + */ + private String tooltip = ""; + + /** + * The image of the component, if there is any. + */ + private Image image; + + /** + * The parent component. + */ + private Component parent; + + /** + * The input definition if there is one. This is used to create the appropriate view. + */ + private InputDefinition inputDefinition; + + /** + * If the component is enabled. + */ + private boolean enabled = true; + + /** + * Gets {@link #name}. + * + * @return {@link #name} + */ + public String getName() { + return name; + } + + /** + * Sets {@link #name}. + * + * @param name + * New value for {@link #name} + */ + public void setName(String name) { + this.name = name; + } + + /** + * Gets {@link #tooltip}. + * + * @return {@link #tooltip} + */ + public String getTooltip() { + return tooltip; + } + + /** + * Sets {@link #tooltip}. + * + * @param tooltip + * New value for {@link #tooltip} + */ + public void setTooltip(String tooltip) { + this.tooltip = tooltip; + } + + /** + * Gets {@link #image}. + * + * @return {@link #image} + */ + public Image getImage() { + return image; + } + + /** + * Sets {@link #image}. + * + * @param image + * New value for {@link #image} + */ + public void setImage(Image image) { + this.image = image; + } + + /** + * Gets {@link #parent}. + * + * @return {@link #parent} + */ + public Component getParent() { + return parent; + } + + /** + * Sets {@link #parent}. + * + * @param parent + * New value for {@link #parent} + */ + public void setParent(Component parent) { + this.parent = parent; + } + + /** + * Gets {@link #inputDefinition}. + * + * @return {@link #inputDefinition} + */ + public InputDefinition getInputDefinition() { + return inputDefinition; + } + + /** + * Sets {@link #inputDefinition}. + * + * @param inputDefinition + * New value for {@link #inputDefinition} + */ + public void setInputDefinition(InputDefinition inputDefinition) { + this.inputDefinition = inputDefinition; + } + + /** + * Gets {@link #enabled}. + * + * @return {@link #enabled} + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Sets {@link #enabled}. + * + * @param enabled + * New value for {@link #enabled} + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(name, tooltip, inputDefinition, parent, enabled); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + Component that = (Component) object; + return Objects.equal(this.name, that.name) && Objects.equal(this.tooltip, that.tooltip) && Objects.equal(this.inputDefinition, that.inputDefinition) && Objects.equal(this.parent, that.parent) + && Objects.equal(this.enabled, that.enabled); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/Composite.java b/inspectIT/src/info/novatec/inspectit/rcp/model/Composite.java new file mode 100644 index 000000000..038d721ea --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/Composite.java @@ -0,0 +1,58 @@ +package info.novatec.inspectit.rcp.model; + +import java.util.ArrayList; +import java.util.List; + +/** + * A composite extends the component functionality by allowing to add children to this node. + * + * @author Patrice Bouillet + * + */ +public class Composite extends Component { + + /** + * The components arranged under this composite node. + */ + private List components = new ArrayList(); + + /** + * Returns all children. + * + * @return The children. + */ + public List getChildren() { + return components; + } + + /** + * Sets the children of this composite. + * + * @param children + * The children to set. + */ + public void setChildren(List children) { + this.components = children; + } + + /** + * Adds a child to this composite at the end of the list. + * + * @param child + * The child to add. + */ + public void addChild(Component child) { + components.add(child); + child.setParent(this); + } + + /** + * Returns true if children are available under this composite. + * + * @return true if children are available. + */ + public boolean hasChildren() { + return !components.isEmpty(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredAgentsComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredAgentsComposite.java new file mode 100644 index 000000000..c0c9e4209 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredAgentsComposite.java @@ -0,0 +1,150 @@ +package info.novatec.inspectit.rcp.model; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.communication.data.cmr.AgentStatusData; +import info.novatec.inspectit.rcp.provider.ICmrRepositoryProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.ui.progress.IElementCollector; + +import com.google.common.base.Objects; + +/** + * This composite holds Agents of one CMR as the deferred children. + * + * @author Ivan Senic + * + */ +public class DeferredAgentsComposite extends DeferredComposite implements ICmrRepositoryProvider { + + /** + * {@link CmrRepositoryDefinition}. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * Defines if so called 'old' agents are being shown. These agents never sent data since the CMR + * started. + */ + private final boolean showOldAgents; + + /** + * Default constructor. + * + * @param cmrRepositoryDefinition + * Repository. + * @param showOldAgents + * Defines if so called 'old' agents are being shown. These agents never sent data + * since the CMR started. + */ + public DeferredAgentsComposite(CmrRepositoryDefinition cmrRepositoryDefinition, boolean showOldAgents) { + this.showOldAgents = showOldAgents; + setRepositoryDefinition(cmrRepositoryDefinition); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void fetchDeferredChildren(Object object, IElementCollector collector, IProgressMonitor monitor) { + monitor.beginTask("Loading agents..", IProgressMonitor.UNKNOWN); + try { + if (cmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.ONLINE) { + Map agents = cmrRepositoryDefinition.getGlobalDataAccessService().getAgentsOverview(); + if (null != agents) { + Map filteredMap = new HashMap(agents.size()); + for (Entry entry : agents.entrySet()) { + PlatformIdent platformIdent = entry.getKey(); + AgentStatusData agentStatusData = entry.getValue(); + // the agentstatusdata is null if the agent wasn't connected before + if (showOldAgents || (!showOldAgents && agentStatusData != null)) { + filteredMap.put(platformIdent, agentStatusData); + } + + } + + List components = AgentFolderFactory.getAgentFolderTree(filteredMap, cmrRepositoryDefinition); + for (Component component : components) { + collector.add(component, monitor); + ((Composite) object).addChild(component); + } + } + } + } finally { + collector.done(); + monitor.done(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public final void setRepositoryDefinition(RepositoryDefinition repositoryDefinition) { + if (repositoryDefinition instanceof CmrRepositoryDefinition) { + this.cmrRepositoryDefinition = (CmrRepositoryDefinition) repositoryDefinition; + } + } + + /** + * {@inheritDoc} + */ + @Override + public RepositoryDefinition getRepositoryDefinition() { + return cmrRepositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public CmrRepositoryDefinition getCmrRepositoryDefinition() { + return cmrRepositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public String getName() { + return cmrRepositoryDefinition.getName(); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), cmrRepositoryDefinition); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + if (!super.equals(object)) { + return false; + } + DeferredAgentsComposite that = (DeferredAgentsComposite) object; + return Objects.equal(this.cmrRepositoryDefinition, that.cmrRepositoryDefinition); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredBrowserComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredBrowserComposite.java new file mode 100644 index 000000000..ad74796b9 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredBrowserComposite.java @@ -0,0 +1,198 @@ +package info.novatec.inspectit.rcp.model; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.progress.IElementCollector; + +import com.google.common.base.Objects; + +/** + * This class only initializes the sub-tree if it is requested. Furthermore, the creation of the + * objects is done piece after piece, so that an immediate visualization can be seen (important for + * sub-trees which are very large). + * + * @author Patrice Bouillet + * + */ +public class DeferredBrowserComposite extends DeferredComposite { + + /** + * The platform ident is used to create the package elements in the sub-tree. + */ + private PlatformIdent platformIdent; + + /** + * The repository definition. + */ + private RepositoryDefinition repositoryDefinition; + + /** + * If inactive instrumentations should be hidden. + */ + private boolean hideInactiveInstrumentations; + + /** + * {@inheritDoc} + */ + @Override + public void fetchDeferredChildren(Object object, IElementCollector collector, IProgressMonitor monitor) { + try { + Composite browserComposite = (Composite) object; + Set methodIdents = platformIdent.getMethodIdents(); + monitor.beginTask("Loading of Package Elements...", IProgressMonitor.UNKNOWN); + Map packageNames = new HashMap(methodIdents.size()); + + for (MethodIdent methodIdent : methodIdents) { + if (select(methodIdent)) { + String packageName = methodIdent.getPackageName(); + if (packageName == null) { + packageName = ""; + } else { + packageName = packageName.trim(); + } + // check if the given package was already added. + if (!packageNames.containsKey(packageName)) { + DeferredPackageComposite composite = getNewChild(); + composite.setRepositoryDefinition(repositoryDefinition); + if ("".equals(packageName)) { + composite.setName("(default)"); + } else { + composite.setName(packageName); + } + + collector.add(composite, monitor); + browserComposite.addChild(composite); + packageNames.put(packageName, composite); + } + + DeferredPackageComposite composite = packageNames.get(packageName); + composite.addClassToDisplay(methodIdent); + composite.setHideInactiveInstrumentations(hideInactiveInstrumentations); + } + + if (monitor.isCanceled()) { + break; + } + } + } finally { + collector.done(); + monitor.done(); + } + } + + /** + * @return Returns the right implementation of the {@link DeferredPackageComposite} to use for + * the child. + */ + protected DeferredPackageComposite getNewChild() { + return new DeferredPackageComposite(); + } + + /** + * Should this method ident pass the selection process. + * + * @param methodIdent + * {@link MethodIdent}. + * @return Should this method ident pass the selection process. + */ + protected boolean select(MethodIdent methodIdent) { + return !hideInactiveInstrumentations || methodIdent.hasActiveSensorTypes(); + } + + /** + * The platform ident used to retrieve these packages. + * + * @param platformIdent + * the platformIdent to set + */ + public void setPlatformIdent(PlatformIdent platformIdent) { + this.platformIdent = platformIdent; + } + + /** + * {@inheritDoc} + */ + @Override + public void setRepositoryDefinition(RepositoryDefinition repositoryDefinition) { + this.repositoryDefinition = repositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public RepositoryDefinition getRepositoryDefinition() { + return repositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public Image getImage() { + if (hideInactiveInstrumentations) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_INSTRUMENTATION_BROWSER); + } else { + return InspectIT.getDefault().getImage(InspectITImages.IMG_INSTRUMENTATION_BROWSER_INACTIVE); + } + } + + /** + * Gets {@link #hideInactiveInstrumentations}. + * + * @return {@link #hideInactiveInstrumentations} + */ + public boolean isHideInactiveInstrumentations() { + return hideInactiveInstrumentations; + } + + /** + * Sets {@link #hideInactiveInstrumentations}. + * + * @param hideInactiveInstrumentations + * New value for {@link #hideInactiveInstrumentations} + */ + public void setHideInactiveInstrumentations(boolean hideInactiveInstrumentations) { + this.hideInactiveInstrumentations = hideInactiveInstrumentations; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), platformIdent, repositoryDefinition); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + if (!super.equals(object)) { + return false; + } + DeferredBrowserComposite that = (DeferredBrowserComposite) object; + return Objects.equal(this.platformIdent, that.platformIdent) && Objects.equal(this.repositoryDefinition, that.repositoryDefinition); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredClassComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredClassComposite.java new file mode 100644 index 000000000..f7a649bb5 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredClassComposite.java @@ -0,0 +1,199 @@ +package info.novatec.inspectit.rcp.model; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.progress.IElementCollector; + +import com.google.common.base.Objects; + +/** + * This class only initializes the sub-tree if it is requested. Furthermore, the creation of the + * objects is done piece after piece, so that an immediate visualization can be seen (important for + * sub-trees which are very large). + * + * @author Patrice Bouillet + * + */ +public class DeferredClassComposite extends DeferredComposite { + + /** + * All the methods which are displayed in the tree. + */ + private List methods = new CopyOnWriteArrayList(); + + /** + * The format string of the output. + */ + protected static final String METHOD_FORMAT = "%s(%s)"; + + /** + * The repository definition. + */ + private RepositoryDefinition repositoryDefinition; + + /** + * If inactive instrumentations should be hidden. + */ + private boolean hideInactiveInstrumentations; + + /** + * {@inheritDoc} + */ + @Override + public void fetchDeferredChildren(Object object, IElementCollector collector, IProgressMonitor monitor) { + try { + Composite classComposite = (Composite) object; + monitor.beginTask("Loading of Method Elements...", methods.size()); + + for (MethodIdent method : methods) { + if (select(method)) { + DeferredMethodComposite composite = new DeferredMethodComposite(); + composite.setRepositoryDefinition(repositoryDefinition); + + if (null != method.getParameters()) { + String parameters = method.getParameters().toString(); + parameters = parameters.substring(1, parameters.length() - 1); + + composite.setName(String.format(METHOD_FORMAT, method.getMethodName(), parameters)); + } else { + composite.setName(String.format(METHOD_FORMAT, method.getMethodName(), "")); + } + composite.setMethod(method); + composite.setHideInactiveInstrumentations(hideInactiveInstrumentations); + + collector.add(composite, monitor); + classComposite.addChild(composite); + } + + monitor.worked(1); + if (monitor.isCanceled()) { + break; + } + } + } finally { + collector.done(); + monitor.done(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEnabled() { + if (CollectionUtils.isNotEmpty(methods)) { + for (MethodIdent methodIdent : methods) { + if (methodIdent.hasActiveSensorTypes()) { + return true; + } + } + } + return false; + } + + /** + * {@inheritDoc} + */ + protected boolean select(MethodIdent methodIdent) { + return !hideInactiveInstrumentations || methodIdent.hasActiveSensorTypes(); + } + + /** + * Adds a method to be displayed later in this sub-tree. + * + * @param methodIdent + * The method to be displayed. + */ + public void addMethodToDisplay(MethodIdent methodIdent) { + methods.add(methodIdent); + } + + /** + * {@inheritDoc} + */ + @Override + public void setRepositoryDefinition(RepositoryDefinition repositoryDefinition) { + this.repositoryDefinition = repositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public RepositoryDefinition getRepositoryDefinition() { + return repositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public Image getImage() { + return InspectIT.getDefault().getImage(InspectITImages.IMG_CLASS); + } + + /** + * @return the methods + */ + protected List getMethods() { + return methods; + } + + /** + * Gets {@link #hideInactiveInstrumentations}. + * + * @return {@link #hideInactiveInstrumentations} + */ + public boolean isHideInactiveInstrumentations() { + return hideInactiveInstrumentations; + } + + /** + * Sets {@link #hideInactiveInstrumentations}. + * + * @param hideInactiveInstrumentations + * New value for {@link #hideInactiveInstrumentations} + */ + public void setHideInactiveInstrumentations(boolean hideInactiveInstrumentations) { + this.hideInactiveInstrumentations = hideInactiveInstrumentations; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), methods, repositoryDefinition); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + if (!super.equals(object)) { + return false; + } + DeferredClassComposite that = (DeferredClassComposite) object; + return Objects.equal(this.methods, that.methods) && Objects.equal(this.repositoryDefinition, that.repositoryDefinition); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredComposite.java new file mode 100644 index 000000000..9b256a760 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredComposite.java @@ -0,0 +1,90 @@ +package info.novatec.inspectit.rcp.model; + +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.ui.progress.IDeferredWorkbenchAdapter; +import org.eclipse.ui.progress.IElementCollector; + +/** + * Abstract class of a deferred composite type where the sub tree is only initialized if it is + * requested. + * + * @author Patrice Bouillet + * + */ +public abstract class DeferredComposite extends Composite implements IDeferredWorkbenchAdapter { + + /** + * {@inheritDoc} + */ + public abstract void fetchDeferredChildren(Object object, IElementCollector collector, IProgressMonitor monitor); + + /** + * Sets the repository definition. + * + * @param repositoryDefinition + * the repository definition. + */ + public abstract void setRepositoryDefinition(RepositoryDefinition repositoryDefinition); + + /** + * Returns the repository definition. + * + * @return the repository definition. + */ + public abstract RepositoryDefinition getRepositoryDefinition(); + + /** + * {@inheritDoc} + */ + public boolean isContainer() { + return true; + } + + /** + * {@inheritDoc} + */ + public Object[] getChildren(Object object) { + return super.getChildren().toArray(); + } + + /** + * {@inheritDoc} + */ + public Object getParent(Object object) { + return super.getParent(); + } + + /** + * {@inheritDoc} + */ + public ImageDescriptor getImageDescriptor(Object object) { + return null; + } + + /** + * {@inheritDoc} + */ + public String getLabel(Object object) { + return super.getName(); + } + + /** + * {@inheritDoc} + */ + public ISchedulingRule getRule(Object object) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return getName(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredMethodComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredMethodComposite.java new file mode 100644 index 000000000..86879a7a7 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredMethodComposite.java @@ -0,0 +1,182 @@ +package info.novatec.inspectit.rcp.model; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.model.MethodIdentToSensorType; +import info.novatec.inspectit.cmr.model.MethodSensorTypeIdent; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData.PartType; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition.IdDefinition; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.progress.IElementCollector; + +import com.google.common.base.Objects; + +/** + * This class only initializes the sub-tree if it is requested. Furthermore, the creation of the + * objects is done piece after piece, so that an immediate visualization can be seen (important for + * sub-trees which are very large). + * + * @author Patrice Bouillet + * + */ +public class DeferredMethodComposite extends DeferredComposite { + + /** + * This method is needed to load all the sensor types for this method. + */ + private MethodIdent method; + + /** + * The repository definition. + */ + private RepositoryDefinition repositoryDefinition; + + /** + * If inactive instrumentations should be hidden. + */ + private boolean hideInactiveInstrumentations; + + /** + * {@inheritDoc} + */ + @Override + public void fetchDeferredChildren(Object object, IElementCollector collector, IProgressMonitor monitor) { + try { + Composite composite = (Composite) object; + Set methodIdentToSensorTypes = method.getMethodIdentToSensorTypes(); + monitor.beginTask("Loading of Sensor Type Elements...", methodIdentToSensorTypes.size()); + + for (MethodIdentToSensorType methodIdentToSensorType : methodIdentToSensorTypes) { + if (!hideInactiveInstrumentations || methodIdentToSensorType.isActive()) { + MethodSensorTypeIdent methodSensorTypeIdent = methodIdentToSensorType.getMethodSensorTypeIdent(); + Component targetSensorType = new Leaf(); + String fqn = methodSensorTypeIdent.getFullyQualifiedClassName(); + SensorTypeEnum sensorTypeEnum = SensorTypeEnum.get(fqn); + targetSensorType.setName(sensorTypeEnum.getDisplayName()); + targetSensorType.setImage(sensorTypeEnum.getImage()); + targetSensorType.setEnabled(methodIdentToSensorType.isActive()); + + if (sensorTypeEnum.isOpenable()) { + InputDefinition inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(repositoryDefinition); + inputDefinition.setId(sensorTypeEnum); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(sensorTypeEnum.getImage()); + editorPropertiesData.setSensorName(sensorTypeEnum.getDisplayName()); + MethodIdent methodIdent = repositoryDefinition.getCachedDataService().getMethodIdentForId(method.getId()); + editorPropertiesData.setViewName(TextFormatter.getMethodString(methodIdent)); + editorPropertiesData.setViewImage(ModifiersImageFactory.getImage(methodIdent.getModifiers())); + editorPropertiesData.setPartNameFlag(PartType.SENSOR); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(method.getPlatformIdent().getId()); + idDefinition.setSensorTypeId(methodSensorTypeIdent.getId()); + idDefinition.setMethodId(method.getId()); + + inputDefinition.setIdDefinition(idDefinition); + targetSensorType.setInputDefinition(inputDefinition); + } + + collector.add(targetSensorType, monitor); + composite.addChild(targetSensorType); + } + monitor.worked(1); + if (monitor.isCanceled()) { + break; + } + } + } finally { + collector.done(); + monitor.done(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEnabled() { + return method.hasActiveSensorTypes(); + } + + /** + * @param method + * the method to set + */ + public void setMethod(MethodIdent method) { + this.method = method; + } + + /** + * {@inheritDoc} + */ + @Override + public void setRepositoryDefinition(RepositoryDefinition repositoryDefinition) { + this.repositoryDefinition = repositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public RepositoryDefinition getRepositoryDefinition() { + return repositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public Image getImage() { + return ModifiersImageFactory.getImage(method.getModifiers()); + } + + /** + * Sets {@link #hideInactiveInstrumentations}. + * + * @param hideInactiveInstrumentations + * New value for {@link #hideInactiveInstrumentations} + */ + public void setHideInactiveInstrumentations(boolean hideInactiveInstrumentations) { + this.hideInactiveInstrumentations = hideInactiveInstrumentations; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), method, repositoryDefinition); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + if (!super.equals(object)) { + return false; + } + DeferredMethodComposite that = (DeferredMethodComposite) object; + return Objects.equal(this.method, that.method) && Objects.equal(this.repositoryDefinition, that.repositoryDefinition); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredPackageComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredPackageComposite.java new file mode 100644 index 000000000..dc1582169 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredPackageComposite.java @@ -0,0 +1,199 @@ +package info.novatec.inspectit.rcp.model; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.progress.IElementCollector; + +import com.google.common.base.Objects; + +/** + * This class only initializes the sub-tree if it is requested. Furthermore, the creation of the + * objects is done piece after piece, so that an immediate visualization can be seen (important for + * sub-trees which are very large). + * + * @author Patrice Bouillet + * + */ +public class DeferredPackageComposite extends DeferredComposite { + + /** + * All the classes which are being displayed in the sub-tree. + */ + private List classes = new CopyOnWriteArrayList(); + + /** + * The repository definition. + */ + private RepositoryDefinition repositoryDefinition; + + /** + * If inactive instrumentations should be hidden. + */ + private boolean hideInactiveInstrumentations; + + /** + * {@inheritDoc} + */ + @Override + public void fetchDeferredChildren(Object object, IElementCollector collector, IProgressMonitor monitor) { + try { + Composite packageComposite = (Composite) object; + monitor.beginTask("Loading of Class Elements...", IProgressMonitor.UNKNOWN); + Map classNames = new HashMap(classes.size()); + + for (MethodIdent clazz : classes) { + if (select(clazz)) { + String className = clazz.getClassName(); + if (!classNames.containsKey(className)) { + DeferredClassComposite composite = getNewChild(); + composite.setRepositoryDefinition(repositoryDefinition); + composite.setName(className); + + collector.add(composite, monitor); + packageComposite.addChild(composite); + classNames.put(className, composite); + } + + DeferredClassComposite composite = classNames.get(className); + composite.addMethodToDisplay(clazz); + composite.setHideInactiveInstrumentations(hideInactiveInstrumentations); + } + if (monitor.isCanceled()) { + break; + } + } + + } finally { + collector.done(); + monitor.done(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEnabled() { + if (CollectionUtils.isNotEmpty(classes)) { + for (MethodIdent methodIdent : classes) { + if (methodIdent.hasActiveSensorTypes()) { + return true; + } + } + } + return false; + } + + /** + * @return Returns the right implementation of the {@link DeferredClassComposite} to use for the + * child. + */ + protected DeferredClassComposite getNewChild() { + return new DeferredClassComposite(); + } + + /** + * Should this method ident pass the selection process. + * + * @param methodIdent + * {@link MethodIdent}. + * @return Should this method ident pass the selection process. + */ + protected boolean select(MethodIdent methodIdent) { + return !hideInactiveInstrumentations || methodIdent.hasActiveSensorTypes(); + } + + /** + * Adds a class which will be displayed in this sub-tree. + * + * @param methodIdent + * The class to be displayed. + */ + public void addClassToDisplay(MethodIdent methodIdent) { + classes.add(methodIdent); + } + + /** + * {@inheritDoc} + */ + @Override + public void setRepositoryDefinition(RepositoryDefinition repositoryDefinition) { + this.repositoryDefinition = repositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public RepositoryDefinition getRepositoryDefinition() { + return repositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public Image getImage() { + return InspectIT.getDefault().getImage(InspectITImages.IMG_PACKAGE); + } + + /** + * Gets {@link #hideInactiveInstrumentations}. + * + * @return {@link #hideInactiveInstrumentations} + */ + public boolean isHideInactiveInstrumentations() { + return hideInactiveInstrumentations; + } + + /** + * Sets {@link #hideInactiveInstrumentations}. + * + * @param hideInactiveInstrumentations + * New value for {@link #hideInactiveInstrumentations} + */ + public void setHideInactiveInstrumentations(boolean hideInactiveInstrumentations) { + this.hideInactiveInstrumentations = hideInactiveInstrumentations; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), classes, repositoryDefinition); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + if (!super.equals(object)) { + return false; + } + DeferredPackageComposite that = (DeferredPackageComposite) object; + return Objects.equal(this.classes, that.classes) && Objects.equal(this.repositoryDefinition, that.repositoryDefinition); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredType.java b/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredType.java new file mode 100644 index 000000000..d9fd5b4d6 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/DeferredType.java @@ -0,0 +1,14 @@ +package info.novatec.inspectit.rcp.model; + +/** + * This enum helps identifying the deferred composite type so that specific actions can be performed + * to populate the children. + * + * @author Patrice Bouillet + * + */ +public enum DeferredType { + + PACKAGE, CLASS, METHOD, SENSOR_TYPE; // NOCHK + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/ExceptionImageFactory.java b/inspectIT/src/info/novatec/inspectit/rcp/model/ExceptionImageFactory.java new file mode 100644 index 000000000..160bc2bc8 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/ExceptionImageFactory.java @@ -0,0 +1,90 @@ +package info.novatec.inspectit.rcp.model; + +import info.novatec.inspectit.communication.ExceptionEvent; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.ResourceManager; +import org.eclipse.jface.viewers.DecorationOverlayIcon; +import org.eclipse.jface.viewers.IDecoration; +import org.eclipse.swt.graphics.Image; + +/** + * Factory currently used for the overlay icons which can be placed on arbitrary images being + * passed. + * + * @author Patrice Bouillet + * + */ +public final class ExceptionImageFactory { + + /** + * The image descriptor for the error overlay. + */ + private static final ImageDescriptor OVERLAY_ERROR = InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_OVERLAY_ERROR); + + /** + * The image descriptor for the priority overlay. + */ + private static final ImageDescriptor OVERLAY_PRIORITY = InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_OVERLAY_PRIORITY); + + /** + * The image descriptor for the up overlay. + */ + private static final ImageDescriptor OVERLAY_UP = InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_OVERLAY_UP); + + /** + * Private constructor. + */ + private ExceptionImageFactory() { + } + + /** + * Returns the image descriptor for the given modifiers. + * + * @param data + * The {@link ExceptionSensorData} data object where to check for the exception event + * type. + * @return The image descriptor. + */ + public static ImageDescriptor getImageDescriptor(ExceptionSensorData data) { + if (null != data) { + ExceptionEvent event = data.getExceptionEvent(); + if (ExceptionEvent.CREATED.equals(event)) { + return OVERLAY_ERROR; + } else if (ExceptionEvent.HANDLED.equals(event)) { + return OVERLAY_PRIORITY; + } else if (ExceptionEvent.PASSED.equals(event)) { + return OVERLAY_UP; + } else if (ExceptionEvent.RETHROWN.equals(event)) { + return OVERLAY_ERROR; + } else if (ExceptionEvent.UNREGISTERED_PASSED.equals(event)) { + return OVERLAY_UP; + } + } + return ImageDescriptor.getMissingImageDescriptor(); + } + + /** + * The passed image will be decorated with an overlay which is defined on the event type of the + * exception transparently. + * + * @param image + * the image that will be decorated with the overlay. + * @param data + * the exception data from which the event will be extracted. + * @param resourceManager + * Resource manager that image will be created with. It is responsibility of a caller + * to provide {@link ResourceManager} for correct image disposing. + * @return the resulting image + */ + public static Image decorateImageWithException(Image image, ExceptionSensorData data, ResourceManager resourceManager) { + ImageDescriptor exceptionDesc = getImageDescriptor(data); + DecorationOverlayIcon icon = new DecorationOverlayIcon(image, exceptionDesc, IDecoration.BOTTOM_RIGHT); + Image createdImage = resourceManager.createImage(icon); + return createdImage; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/FilteredDeferredBrowserComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/model/FilteredDeferredBrowserComposite.java new file mode 100644 index 000000000..4d638c82e --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/FilteredDeferredBrowserComposite.java @@ -0,0 +1,84 @@ +package info.novatec.inspectit.rcp.model; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.model.MethodIdentToSensorType; +import info.novatec.inspectit.util.ObjectUtils; + +import com.google.common.base.Objects; + +/** + * Filtered package composite delegates the children creation to the + * {@link FilteredDeferredPackageComposite}. + * + * @author Ivan Senic + * + */ +public class FilteredDeferredBrowserComposite extends DeferredBrowserComposite { + + /** + * Sensor to show. + */ + private SensorTypeEnum sensorTypeEnumToShow; + + /** + * @param sensorTypeEnum + * Set the sensor type to show. + */ + public FilteredDeferredBrowserComposite(SensorTypeEnum sensorTypeEnum) { + this.sensorTypeEnumToShow = sensorTypeEnum; + } + + /** + * {@inheritDoc} + */ + @Override + protected DeferredPackageComposite getNewChild() { + return new FilteredDeferredPackageComposite(sensorTypeEnumToShow); + } + + /** + * {@inheritDoc} + */ + @Override + protected boolean select(MethodIdent methodIdent) { + for (MethodIdentToSensorType methodIdentToSensorType : methodIdent.getMethodIdentToSensorTypes()) { + SensorTypeEnum sensorTypeEnum = SensorTypeEnum.get(methodIdentToSensorType.getMethodSensorTypeIdent().getFullyQualifiedClassName()); + if (ObjectUtils.equals(sensorTypeEnum, sensorTypeEnumToShow)) { + if (!isHideInactiveInstrumentations() || methodIdentToSensorType.isActive()) { + return super.select(methodIdent); + } + } + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), sensorTypeEnumToShow); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + if (!super.equals(object)) { + return false; + } + FilteredDeferredBrowserComposite that = (FilteredDeferredBrowserComposite) object; + return Objects.equal(this.sensorTypeEnumToShow, that.sensorTypeEnumToShow); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/FilteredDeferredClassComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/model/FilteredDeferredClassComposite.java new file mode 100644 index 000000000..892ece6e7 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/FilteredDeferredClassComposite.java @@ -0,0 +1,150 @@ +package info.novatec.inspectit.rcp.model; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.model.MethodIdentToSensorType; +import info.novatec.inspectit.cmr.model.MethodSensorTypeIdent; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition.IdDefinition; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.List; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.ui.progress.IElementCollector; + +import com.google.common.base.Objects; + +/** + * This composite shows only one sensor for each method of the class that has a + * {@link SensorTypeEnum} type. + * + * @author Ivan Senic + * + */ +public class FilteredDeferredClassComposite extends DeferredClassComposite { + + /** + * Sensor to show. + */ + private SensorTypeEnum sensorTypeEnumToShow; + + /** + * @param sensorTypeEnum + * Set the sensor type to show. + */ + public FilteredDeferredClassComposite(SensorTypeEnum sensorTypeEnum) { + this.sensorTypeEnumToShow = sensorTypeEnum; + } + + /** + * {@inheritDoc} + */ + @Override + public void fetchDeferredChildren(Object object, IElementCollector collector, IProgressMonitor monitor) { + try { + List methods = getMethods(); + Composite classComposite = (Composite) object; + monitor.beginTask("Loading of Method Elements...", methods.size()); + for (MethodIdent method : methods) { + for (MethodIdentToSensorType methodIdentToSensorType : method.getMethodIdentToSensorTypes()) { + if (!isHideInactiveInstrumentations() || methodIdentToSensorType.isActive()) { + MethodSensorTypeIdent methodSensorTypeIdent = methodIdentToSensorType.getMethodSensorTypeIdent(); + String fqn = methodSensorTypeIdent.getFullyQualifiedClassName(); + SensorTypeEnum sensorTypeEnum = SensorTypeEnum.get(fqn); + if (sensorTypeEnum == sensorTypeEnumToShow) { // NOPMD + if (sensorTypeEnum.isOpenable()) { + Component targetSensorType = new Leaf(); + targetSensorType.setEnabled(methodIdentToSensorType.isActive()); + if (null != method.getParameters()) { + String parameters = method.getParameters().toString(); + parameters = parameters.substring(1, parameters.length() - 1); + + targetSensorType.setName(String.format(METHOD_FORMAT, method.getMethodName(), parameters)); + } else { + targetSensorType.setName(String.format(METHOD_FORMAT, method.getMethodName(), "")); + } + targetSensorType.setImage(ModifiersImageFactory.getImage(method.getModifiers())); + + InputDefinition inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(getRepositoryDefinition()); + inputDefinition.setId(sensorTypeEnum); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(sensorTypeEnum.getImage()); + editorPropertiesData.setSensorName(sensorTypeEnum.getDisplayName()); + editorPropertiesData.setViewName(TextFormatter.getMethodString(method)); + editorPropertiesData.setViewImage(ModifiersImageFactory.getImage(method.getModifiers())); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(method.getPlatformIdent().getId()); + idDefinition.setSensorTypeId(methodSensorTypeIdent.getId()); + idDefinition.setMethodId(method.getId()); + + inputDefinition.setIdDefinition(idDefinition); + targetSensorType.setInputDefinition(inputDefinition); + + collector.add(targetSensorType, monitor); + classComposite.addChild(targetSensorType); + } + break; + } + } + } + + monitor.worked(1); + if (monitor.isCanceled()) { + break; + } + } + } finally { + monitor.done(); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected boolean select(MethodIdent methodIdent) { + for (MethodIdentToSensorType methodIdentToSensorType : methodIdent.getMethodIdentToSensorTypes()) { + SensorTypeEnum sensorTypeEnum = SensorTypeEnum.get(methodIdentToSensorType.getMethodSensorTypeIdent().getFullyQualifiedClassName()); + if (ObjectUtils.equals(sensorTypeEnum, sensorTypeEnumToShow)) { + return super.select(methodIdent); + } + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), sensorTypeEnumToShow); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + if (!super.equals(object)) { + return false; + } + FilteredDeferredClassComposite that = (FilteredDeferredClassComposite) object; + return Objects.equal(this.sensorTypeEnumToShow, that.sensorTypeEnumToShow); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/FilteredDeferredPackageComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/model/FilteredDeferredPackageComposite.java new file mode 100644 index 000000000..a89305352 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/FilteredDeferredPackageComposite.java @@ -0,0 +1,84 @@ +package info.novatec.inspectit.rcp.model; + +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.model.MethodIdentToSensorType; +import info.novatec.inspectit.util.ObjectUtils; + +import com.google.common.base.Objects; + +/** + * Filtered package composite delegates the children creation to the + * {@link FilteredDeferredClassComposite}. + * + * @author Ivan Senic + * + */ +public class FilteredDeferredPackageComposite extends DeferredPackageComposite { + + /** + * Sensor to show. + */ + private SensorTypeEnum sensorTypeEnumToShow; + + /** + * @param sensorTypeEnum + * Set the sensor type to show. + */ + public FilteredDeferredPackageComposite(SensorTypeEnum sensorTypeEnum) { + this.sensorTypeEnumToShow = sensorTypeEnum; + } + + /** + * {@inheritDoc} + */ + @Override + protected DeferredClassComposite getNewChild() { + return new FilteredDeferredClassComposite(sensorTypeEnumToShow); + } + + /** + * {@inheritDoc} + */ + @Override + protected boolean select(MethodIdent methodIdent) { + for (MethodIdentToSensorType methodIdentToSensorType : methodIdent.getMethodIdentToSensorTypes()) { + SensorTypeEnum sensorTypeEnum = SensorTypeEnum.get(methodIdentToSensorType.getMethodSensorTypeIdent().getFullyQualifiedClassName()); + if (ObjectUtils.equals(sensorTypeEnum, sensorTypeEnumToShow)) { + if (!isHideInactiveInstrumentations() || methodIdentToSensorType.isActive()) { + return super.select(methodIdent); + } + } + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), sensorTypeEnumToShow); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + if (!super.equals(object)) { + return false; + } + FilteredDeferredPackageComposite that = (FilteredDeferredPackageComposite) object; + return Objects.equal(this.sensorTypeEnumToShow, that.sensorTypeEnumToShow); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/GroupedLabelsComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/model/GroupedLabelsComposite.java new file mode 100644 index 000000000..391d24e44 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/GroupedLabelsComposite.java @@ -0,0 +1,71 @@ +package info.novatec.inspectit.rcp.model; + +import info.novatec.inspectit.storage.label.AbstractStorageLabel; + +/** + * Composite for storage leafs when grouping by labels is used. + * + * @author Ivan Senic + * + */ +public class GroupedLabelsComposite extends Composite implements Comparable { + + /** + * Example label to represent the group. + */ + private AbstractStorageLabel exampleLabel; + + /** + * No-arg constructor. + */ + public GroupedLabelsComposite() { + } + + /** + * Default constructor. + * + * @param label + * Example label to represent the group or null if this group can not be + * represent by label. + */ + public GroupedLabelsComposite(AbstractStorageLabel label) { + super(); + this.exampleLabel = label; + } + + /** + * Gets {@link #exampleLabel}. + * + * @return {@link #exampleLabel} + */ + public AbstractStorageLabel getLabel() { + return exampleLabel; + } + + /** + * Sets {@link #exampleLabel}. + * + * @param label + * New value for {@link #exampleLabel} + */ + public void setLabel(AbstractStorageLabel label) { + this.exampleLabel = label; + } + + /** + * {@inheritDoc} + */ + @Override + public int compareTo(GroupedLabelsComposite other) { + if (null != exampleLabel && null != other.exampleLabel) { + return exampleLabel.compareTo(other.exampleLabel); + } else if (null == exampleLabel) { + return 1; + } else if (null == other.exampleLabel) { + return -1; + } else { + return 0; + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/Leaf.java b/inspectIT/src/info/novatec/inspectit/rcp/model/Leaf.java new file mode 100644 index 000000000..0fea01eea --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/Leaf.java @@ -0,0 +1,11 @@ +package info.novatec.inspectit.rcp.model; + +/** + * Leaf is the {@link Component} that can not have children. + * + * @author Ivan Senic + * + */ +public class Leaf extends Component { + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/Modifier.java b/inspectIT/src/info/novatec/inspectit/rcp/model/Modifier.java new file mode 100644 index 000000000..638167f09 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/Modifier.java @@ -0,0 +1,215 @@ +package info.novatec.inspectit.rcp.model; + +/** + * The Modifier class provides static methods and constants to decode class and member access + * modifiers. The constant values are equivalent to the corresponding values in {@link AccessFlag}. + *

+ * All the methods/constants in this class are compatible with ones in + * java.lang.reflect.Modifier. + *

+ * IMPORTANT: The class code is copied/taken/based from javassist. Original author is Shigeru + * Chiba. License info can be found here. + */ +public final class Modifier { + public static final int PUBLIC = AccessFlag.PUBLIC; // NOCHK + public static final int PRIVATE = AccessFlag.PRIVATE; // NOCHK + public static final int PROTECTED = AccessFlag.PROTECTED;// NOCHK + public static final int STATIC = AccessFlag.STATIC;// NOCHK + public static final int FINAL = AccessFlag.FINAL;// NOCHK + public static final int SYNCHRONIZED = AccessFlag.SYNCHRONIZED;// NOCHK + public static final int VOLATILE = AccessFlag.VOLATILE;// NOCHK + public static final int TRANSIENT = AccessFlag.TRANSIENT;// NOCHK + public static final int NATIVE = AccessFlag.NATIVE;// NOCHK + public static final int INTERFACE = AccessFlag.INTERFACE;// NOCHK + public static final int ABSTRACT = AccessFlag.ABSTRACT;// NOCHK + public static final int STRICT = AccessFlag.STRICT;// NOCHK + public static final int ANNOTATION = AccessFlag.ANNOTATION;// NOCHK + public static final int ENUM = AccessFlag.ENUM;// NOCHK + + /** + * Private constructor for utility class. + */ + private Modifier() { + } + + /** + * Returns true if the modifiers include the public modifier. + * + * @param mod + * modifier flags. + * @return true if the modifiers include the public modifier. + */ + public static boolean isPublic(int mod) { + return (mod & PUBLIC) != 0; + } + + /** + * Returns true if the modifiers include the private modifier. + * + * @param mod + * modifier flags. + * @return true if the modifiers include the private modifier. + */ + public static boolean isPrivate(int mod) { + return (mod & PRIVATE) != 0; + } + + /** + * Returns true if the modifiers include the protected modifier. + * + * @param mod + * modifier flags. + * @return true if the modifiers include the protected modifier. + */ + public static boolean isProtected(int mod) { + return (mod & PROTECTED) != 0; + } + + /** + * Returns true if the modifiers do not include either public, protected, or + * private. + * + * @param mod + * modifier flags. + * @return true if the modifiers do not include either public, protected, or + * private. + */ + public static boolean isPackage(int mod) { + return (mod & (PUBLIC | PRIVATE | PROTECTED)) == 0; + } + + /** + * Returns true if the modifiers include the static modifier. + * + * @param mod + * modifier flags. + * @return true if the modifiers include the static modifier. + */ + public static boolean isStatic(int mod) { + return (mod & STATIC) != 0; + } + + /** + * Returns true if the modifiers include the final modifier. + * + * @param mod + * modifier flags. + * @return true if the modifiers include the final modifier. + */ + public static boolean isFinal(int mod) { + return (mod & FINAL) != 0; + } + + /** + * Returns true if the modifiers include the synchronized modifier. + * + * @param mod + * modifier flags. + * @return true if the modifiers include the synchronized modifier. + */ + public static boolean isSynchronized(int mod) { + return (mod & SYNCHRONIZED) != 0; + } + + /** + * Returns true if the modifiers include the volatile modifier. + * + * @param mod + * modifier flags. + * @return true if the modifiers include the volatile modifier. + */ + public static boolean isVolatile(int mod) { + return (mod & VOLATILE) != 0; + } + + /** + * Returns true if the modifiers include the transient modifier. + * + * @param mod + * modifier flags. + * @return true if the modifiers include the transient modifier. + */ + public static boolean isTransient(int mod) { + return (mod & TRANSIENT) != 0; + } + + /** + * Returns true if the modifiers include the native modifier. + * + * @param mod + * modifier flags. + * @return true if the modifiers include the native modifier. + */ + public static boolean isNative(int mod) { + return (mod & NATIVE) != 0; + } + + /** + * Returns true if the modifiers include the interface modifier. + * + * @param mod + * modifier flags. + * @return true if the modifiers include the interface modifier. + */ + public static boolean isInterface(int mod) { + return (mod & INTERFACE) != 0; + } + + /** + * Returns true if the modifiers include the annotation modifier. + * + * @param mod + * modifier flags. + * @return true if the modifiers include the annotation modifier. + */ + public static boolean isAnnotation(int mod) { + return (mod & ANNOTATION) != 0; + } + + /** + * Returns true if the modifiers include the enum modifier. + * + * @param mod + * modifier flags. + * @return true if the modifiers include the enum modifier. + */ + public static boolean isEnum(int mod) { + return (mod & ENUM) != 0; + } + + /** + * Returns true if the modifiers include the abstract modifier. + * + * @param mod + * modifier flags. + * @return true if the modifiers include the abstract modifier. + */ + public static boolean isAbstract(int mod) { + return (mod & ABSTRACT) != 0; + } + + /** + * Returns true if the modifiers include the strictfp modifier. + * + * @param mod + * modifier flags. + * @return true if the modifiers include the strictfp modifier. + */ + public static boolean isStrict(int mod) { + return (mod & STRICT) != 0; + } + + /** + * Return a string describing the access modifier flags in the specified modifier. + * + * @param mod + * modifier flags. + * @return a string describing the access modifier flags in the specified modifier. + */ + public static String toString(int mod) { + return java.lang.reflect.Modifier.toString(mod); + } + +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/ModifiersImageFactory.java b/inspectIT/src/info/novatec/inspectit/rcp/model/ModifiersImageFactory.java new file mode 100644 index 000000000..74a7629af --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/ModifiersImageFactory.java @@ -0,0 +1,68 @@ +package info.novatec.inspectit.rcp.model; + +import static info.novatec.inspectit.rcp.model.Modifier.isPackage; +import static info.novatec.inspectit.rcp.model.Modifier.isPrivate; +import static info.novatec.inspectit.rcp.model.Modifier.isProtected; +import static info.novatec.inspectit.rcp.model.Modifier.isPublic; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; + +import org.eclipse.swt.graphics.Image; + +/** + * Creates the appropriate method visibility image for the int value. + * + * @author Patrice Bouillet + * + */ +public final class ModifiersImageFactory { + + /** + * The image key for the private visibility method. + */ + private static final String METHOD_PRIV_IMAGE = InspectITImages.IMG_METHOD_PRIVATE; + + /** + * The image key for the default visibility method. + */ + private static final String METHOD_DEFAULT_IMAGE = InspectITImages.IMG_METHOD_DEFAULT; + + /** + * The image key for the protected visibility method. + */ + private static final String METHOD_PROT_IMAGE = InspectITImages.IMG_METHOD_PROTECTED; + + /** + * The image key for the public visibility method. + */ + private static final String METHOD_PUB_IMAGE = InspectITImages.IMG_METHOD_PUBLIC; + + /** + * Hide constructor to disallow instantiation. + */ + private ModifiersImageFactory() { + } + + /** + * Returns the image for the given modifiers. This image should not be disposed. + * + * @param modifiers + * The modifiers. + * @return The image. + */ + public static Image getImage(int modifiers) { + InspectIT inspectIT = InspectIT.getDefault(); + if (isPrivate(modifiers)) { + return inspectIT.getImage(METHOD_PRIV_IMAGE); + } else if (isPackage(modifiers)) { + return inspectIT.getImage(METHOD_DEFAULT_IMAGE); + } else if (isProtected(modifiers)) { + return inspectIT.getImage(METHOD_PROT_IMAGE); + } else if (isPublic(modifiers)) { + return inspectIT.getImage(METHOD_PUB_IMAGE); + } + + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/SensorTypeEnum.java b/inspectIT/src/info/novatec/inspectit/rcp/model/SensorTypeEnum.java new file mode 100644 index 000000000..87a787aaf --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/SensorTypeEnum.java @@ -0,0 +1,217 @@ +package info.novatec.inspectit.rcp.model; + +import info.novatec.inspectit.cmr.model.MethodIdentToSensorType; +import info.novatec.inspectit.cmr.model.SensorTypeIdent; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.eclipse.swt.graphics.Image; + +/** + * This enumeration holds all available sensor types with their full qualified name and their image. + * + * @author Patrice Bouillet + * + */ +public enum SensorTypeEnum { + /** The timer sensor type. */ + TIMER("info.novatec.inspectit.agent.sensor.method.timer.TimerSensor", InspectITImages.IMG_TIMER), + /** The average timer sensor type. */ + AVERAGE_TIMER("info.novatec.inspectit.agent.sensor.method.averagetimer.AverageTimerSensor", InspectITImages.IMG_TIMER), + /** Charting with single timers. */ + CHARTING_TIMER("info.novatec.inspectit.agent.sensor.method.averagetimer.ChartingTimer", InspectITImages.IMG_TIMER), + /** Charting with multiple timers. */ + CHARTING_MULTI_TIMER("info.novatec.inspectit.agent.sensor.method.averagetimer.ChartingMultiTimer", InspectITImages.IMG_TIMER), + /** The invocation sequence sensor type. */ + INVOCATION_SEQUENCE("info.novatec.inspectit.agent.sensor.method.invocationsequence.InvocationSequenceSensor", InspectITImages.IMG_INVOCATION), + /** The sql sensor type. */ + SQL("info.novatec.inspectit.agent.sensor.method.jdbc.SQLTimerSensor", InspectITImages.IMG_DATABASE), + /** The jdbc connection sensor type. */ + JDBC_CONNECTION("info.novatec.inspectit.agent.sensor.method.jdbc.ConnectionSensor", InspectITImages.IMG_DATABASE, false), + /** Meta data from the connection. */ + JDBC_CONNECTION_META_DATA("info.novatec.inspectit.agent.sensor.method.jdbc.ConnectionMetaDataSensor", InspectITImages.IMG_DATABASE, false), + /** The jdbc statement sensor type. */ + JDBC_STATEMENT("info.novatec.inspectit.agent.sensor.method.jdbc.StatementSensor", InspectITImages.IMG_DATABASE, false), + /** The jdbc prepared statement sensor type. */ + JDBC_PREPARED_STATEMENT("info.novatec.inspectit.agent.sensor.method.jdbc.PreparedStatementSensor", InspectITImages.IMG_DATABASE, false), + /** The jdbc prepared statement parameter sensor type. */ + JDBC_PREPARED_STATEMENT_PARAMETER("info.novatec.inspectit.agent.sensor.method.jdbc.PreparedStatementParameterSensor", InspectITImages.IMG_DATABASE, false), + /** The exception sensor. */ + EXCEPTION_SENSOR("info.novatec.inspectit.agent.sensor.exception.ExceptionSensor", InspectITImages.IMG_EXCEPTION_SENSOR), + /** The exception sensor overview. */ + EXCEPTION_SENSOR_GROUPED("info.novatec.inspectit.agent.sensor.exception.ExceptionSensorOverview", InspectITImages.IMG_EXCEPTION_SENSOR), + /** The combined metrics sensor type. */ + MARVIN_WORKFLOW("info.novatec.inspectit.agent.sensor.method.marvintimer.MarvinWorkflowSensor", InspectITImages.IMG_INVOCATION), + /** The Http timer sensor type. */ + HTTP_TIMER_SENSOR("info.novatec.inspectit.agent.sensor.method.http.HttpSensor", InspectITImages.IMG_HTTP), + /** The Http timer sensor type. */ + TAGGED_HTTP_TIMER_SENSOR("info.novatec.inspectit.agent.sensor.method.http.HttpSensor", InspectITImages.IMG_HTTP), + /** The charting Http timer sensor type. */ + CHARTING_HTTP_TIMER_SENSOR("info.novatec.inspectit.agent.sensor.method.http.ChartingHttpSensor", InspectITImages.IMG_HTTP), + /** The classloading information sensor type. */ + CLASSLOADING_INFORMATION("info.novatec.inspectit.agent.sensor.platform.ClassLoadingInformation", InspectITImages.IMG_CLASS_OVERVIEW), + /** The compilation information sensor type. */ + COMPILATION_INFORMATION("info.novatec.inspectit.agent.sensor.platform.CompilationInformation", null), + /** The memory information sensor type. */ + MEMORY_INFORMATION("info.novatec.inspectit.agent.sensor.platform.MemoryInformation", InspectITImages.IMG_MEMORY_OVERVIEW), + /** The cpu information sensor type. */ + CPU_INFORMATION("info.novatec.inspectit.agent.sensor.platform.CpuInformation", InspectITImages.IMG_CPU_OVERVIEW), + /** The runtime information sensor type. */ + RUNTIME_INFORMATION("info.novatec.inspectit.agent.sensor.platform.RuntimeInformation", null), + /** The system information sensor type. */ + SYSTEM_INFORMATION("info.novatec.inspectit.agent.sensor.platform.SystemInformation", InspectITImages.IMG_SYSTEM_OVERVIEW), + /** The thread information sensor type. */ + THREAD_INFORMATION("info.novatec.inspectit.agent.sensor.platform.ThreadInformation", InspectITImages.IMG_THREADS_OVERVIEW), + /** The navigation invocation sequence sensor type. */ + NAVIGATION_INVOCATION("info.novatec.inspectit.agent.sensor.method.invocationsequence.NavigationInvocationSequenceSensor", InspectITImages.IMG_INVOCATION), + /** The multi invocation timer data sensor type. */ + MULTI_INVOC_DATA("info.novatec.inspectit.agent.sensor.method.MultiInvocSensor", InspectITImages.IMG_INVOCATION); + + /** + * The LOOKUP map which is used to get an element of the enumeration when passing the full + * qualified name. + */ + private static final Map LOOKUP = new HashMap(); + + static { + for (SensorTypeEnum s : EnumSet.allOf(SensorTypeEnum.class)) { + LOOKUP.put(s.getFqn(), s); + } + } + + /** + * The full qualified name string. + */ + private String fqn; + + /** + * The image descriptor. + */ + private Image image; + + /** + * Defines if this sensor type can be opened somehow. By default true. + */ + private boolean openable = true; + + /** + * Constructs an element of the enumeration with the given full qualified name string. + * + * @param fqn + * The full qualified name. + * @param imageName + * The name of the image. Names are defined in {@link InspectITImages}. + */ + private SensorTypeEnum(String fqn, String imageName) { + this.fqn = fqn; + this.image = InspectIT.getDefault().getImage(imageName); + } + + /** + * Same as standard constructor but the openable can be specified in addition. + * + * @param fqn + * The full qualified name. + * @param imageName + * The name of the image. Names are defined in {@link InspectITImages}. + * @param openable + * Defines if this can be opened somehow in the UI. + */ + private SensorTypeEnum(String fqn, String imageName, boolean openable) { + this.fqn = fqn; + this.image = InspectIT.getDefault().getImage(imageName); + this.openable = openable; + } + + /** + * The full qualified name of the sensor type. + * + * @return The full qualified name. + */ + public String getFqn() { + return fqn; + } + + /** + * Returns an element of the enumeration for the given full qualified name. + * + * @param fqn + * The full qualified class name of the sensor type. + * @return An element of the enumeration. + */ + public static SensorTypeEnum get(String fqn) { + return LOOKUP.get(fqn); + } + + /** + * Returns all elements of the enumeration for the given list of {@link MethodIdentToSensorType} + * objects. + * + * @param methodIdentToSensorTypes + * The passed {@link MethodIdentToSensorType} objects. + * @return A set of SensorTypeEnum objects. + */ + public static Set getAllOf(Set methodIdentToSensorTypes) { + Set sensorTypeSet = EnumSet.noneOf(SensorTypeEnum.class); + for (MethodIdentToSensorType methodIdentToSensorType : methodIdentToSensorTypes) { + SensorTypeIdent sensorType = methodIdentToSensorType.getMethodSensorTypeIdent(); + SensorTypeEnum sensorTypeEnum = get(sensorType.getFullyQualifiedClassName()); + if (null != sensorTypeEnum) { + sensorTypeSet.add(sensorTypeEnum); + } else { + // This might happen if we realize a new sensor type and forget to add it to the + // sensor type enum. We put this here as a failfast reminder. + throw new RuntimeException("Lookup for the enum of sensor type " + sensorType.getFullyQualifiedClassName() + " fails"); + } + } + return sensorTypeSet; + } + + /** + * Returns an image descriptor for this sensor type. + * + * @return The sensor type image descriptor. + */ + public Image getImage() { + return image; + } + + /** + * Returns a displayable name of the sensor type. + * + * @return The displayable name. + */ + public String getDisplayName() { + StringBuilder name = new StringBuilder(name().toLowerCase().replaceAll("_", " ")); + Character character = name.charAt(0); + character = Character.toUpperCase(character); + name.setCharAt(0, character); + + int i = 0; + while (i >= 0) { + i = name.indexOf(" ", i); + if (i >= 0) { + i = i + 1; + character = Character.toUpperCase(name.charAt(i)); + name.setCharAt(i, character); + } + } + + return name.toString(); + } + + /** + * Returns if this sensor type can be opened somehow. + * + * @return if its openable. + */ + public boolean isOpenable() { + return openable; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/TreeModelManager.java b/inspectIT/src/info/novatec/inspectit/rcp/model/TreeModelManager.java new file mode 100644 index 000000000..0c21111e8 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/TreeModelManager.java @@ -0,0 +1,725 @@ +package info.novatec.inspectit.rcp.model; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.cmr.model.PlatformSensorTypeIdent; +import info.novatec.inspectit.cmr.model.SensorTypeIdent; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData; +import info.novatec.inspectit.rcp.editor.inputdefinition.EditorPropertiesData.PartType; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition.IdDefinition; +import info.novatec.inspectit.rcp.formatter.SensorTypeAvailabilityEnum; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.runtime.Assert; + +/** + * The manager is used to create a tree model currently used by the {@link ServerView}. + * + * @author Patrice Bouillet + * @author Eduard Tudenh��fner + * @author Stefan Siegl + */ +public class TreeModelManager { + + /** + * The repository definition used by this tree. + */ + private RepositoryDefinition repositoryDefinition; + + /** + * Platform ident. + */ + private PlatformIdent platformIdent; + + /** + * If inactive instrumentation should be hidden. + */ + private boolean hideInactiveInstrumentations; + + /** + * Every tree model manager needs a reference to a {@link RepositoryDefinition} which reflects a + * CMR. + * + * @param repositoryDefinition + * The definition of the repository / CMR. + * @param platformIdent + * {@link PlatformIdent} to create tree for. + * @param hideInactiveInstrumentations + * If inactive instrumentation should be hidden. + */ + public TreeModelManager(RepositoryDefinition repositoryDefinition, PlatformIdent platformIdent, boolean hideInactiveInstrumentations) { + Assert.isNotNull(repositoryDefinition); + + this.repositoryDefinition = repositoryDefinition; + this.platformIdent = platformIdent; + this.hideInactiveInstrumentations = hideInactiveInstrumentations; + } + + /** + * Returns the root elements of this model. + * + * @return The root elements. + */ + public Object[] getRootElements() { + List components = new ArrayList(); + if (null != platformIdent) { + // Add all sub-trees to this Agent + components.add(getInstrumentedMethodsTree(platformIdent, repositoryDefinition)); + components.add(getInvocationSequenceTree(platformIdent, repositoryDefinition)); + components.add(getSqlTree(platformIdent, repositoryDefinition)); + components.add(getTimerTree(platformIdent, repositoryDefinition)); + components.add(getHttpTimerTree(platformIdent, repositoryDefinition)); + components.add(getExceptionSensorTree(platformIdent, repositoryDefinition)); + components.add(getSystemOverviewTree(platformIdent, repositoryDefinition)); + } + return components.toArray(); + } + + /** + * Creates the deferred sub-tree for instrumented methods. + * + * @param platformIdent + * The platform ID used to create the sub-tree. + * @param definition + * The {@link RepositoryDefinition} object. + * @return a list containing the root and all children representing the instrumented methods in + * the target VM. + */ + protected Component getInstrumentedMethodsTree(PlatformIdent platformIdent, RepositoryDefinition definition) { + DeferredBrowserComposite instrumentedMethods = new DeferredBrowserComposite(); + instrumentedMethods.setName("Instrumentation Browser"); + instrumentedMethods.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_INSTRUMENTATION_BROWSER)); + instrumentedMethods.setPlatformIdent(platformIdent); + instrumentedMethods.setRepositoryDefinition(definition); + instrumentedMethods.setHideInactiveInstrumentations(hideInactiveInstrumentations); + instrumentedMethods.setTooltip("In this tree, you can see all methods that were being instrumented since the first launch of the Agent. " + + "It does not necessarily mean that these methods are currently instrumented and gathering data."); + + return instrumentedMethods; + } + + /** + * Returns the invocation sequence tree. + * + * @param platformIdent + * The platform ident used to create the tree. + * @param definition + * The {@link RepositoryDefinition} object. + * @return The invocation sequence tree. + */ + protected Component getInvocationSequenceTree(PlatformIdent platformIdent, RepositoryDefinition definition) { + Composite invocationSequence = new Composite(); + invocationSequence.setName("Invocation Sequences"); + invocationSequence.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_INVOCATION)); + invocationSequence.setTooltip("Invocation Sequences are recorded call trees of the application. Only the starting points (which are defined " + + "via the invocation sequence sensor in the agent configuration) are shown in the browser tree."); + + Component showAll = new Leaf(); + showAll.setName("Show All"); + showAll.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SHOW_ALL)); + + InputDefinition inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(definition); + inputDefinition.setId(SensorTypeEnum.INVOCATION_SEQUENCE); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(SensorTypeEnum.INVOCATION_SEQUENCE.getImage()); + editorPropertiesData.setSensorName("Invocation Sequences"); + editorPropertiesData.setViewImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SHOW_ALL)); + editorPropertiesData.setViewName("Show All"); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(platformIdent.getId()); + + inputDefinition.setIdDefinition(idDefinition); + showAll.setInputDefinition(inputDefinition); + + FilteredDeferredBrowserComposite browser = new FilteredDeferredBrowserComposite(SensorTypeEnum.INVOCATION_SEQUENCE); + browser.setPlatformIdent(platformIdent); + browser.setRepositoryDefinition(repositoryDefinition); + browser.setName("Browser"); + browser.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_INSTRUMENTATION_BROWSER)); + browser.setTooltip("Only the starting points of invocation sequences (which are defined via the invocation sequence sensor in the agent configuration) are shown in this tree."); + browser.setHideInactiveInstrumentations(hideInactiveInstrumentations); + + invocationSequence.addChild(showAll); + invocationSequence.addChild(browser); + + return invocationSequence; + } + + /** + * + * Returns the SQL tree. + * + * @param platformIdent + * The platform ident used to create the tree. + * @param definition + * The {@link RepositoryDefinition} object. + * @return The sql tree. + */ + private Component getSqlTree(PlatformIdent platformIdent, RepositoryDefinition definition) { + Composite invocationSequence = new Composite(); + invocationSequence.setName("SQL Statements"); + invocationSequence.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_DATABASE)); + invocationSequence.setTooltip("All recorded SQL statements can be analyzed here."); + + Component showAll = new Leaf(); + showAll.setName("Show All"); + showAll.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SHOW_ALL)); + + InputDefinition inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(definition); + inputDefinition.setId(SensorTypeEnum.SQL); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorName("SQL Statements"); + editorPropertiesData.setSensorImage(SensorTypeEnum.SQL.getImage()); + editorPropertiesData.setViewName("Show All"); + editorPropertiesData.setViewImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SHOW_ALL)); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(platformIdent.getId()); + + inputDefinition.setIdDefinition(idDefinition); + showAll.setInputDefinition(inputDefinition); + + invocationSequence.addChild(showAll); + + return invocationSequence; + } + + /** + * Creates the sub-tree for the platform sensors. + * + * @param platformIdent + * The platform ident. + * @param definition + * The {@link RepositoryDefinition} object. + * @return An instance of {@link Component}. + */ + private Component getSystemOverviewTree(PlatformIdent platformIdent, RepositoryDefinition definition) { + Composite systemOverview = new Composite(); + systemOverview.setName("System Overview"); + systemOverview.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SYSTEM_OVERVIEW)); + + Set sensorTypeIdents = platformIdent.getSensorTypeIdents(); + List platformSensorTypeIdentList = new ArrayList(); + + // get all platform sensor types + for (SensorTypeIdent sensorTypeIdent : sensorTypeIdents) { + if (sensorTypeIdent instanceof PlatformSensorTypeIdent) { + PlatformSensorTypeIdent platformSensorTypeIdent = (PlatformSensorTypeIdent) sensorTypeIdent; + platformSensorTypeIdentList.add(platformSensorTypeIdent); + } + } + + // sort the platform sensor types + Collections.sort(platformSensorTypeIdentList, new Comparator() { + public int compare(PlatformSensorTypeIdent one, PlatformSensorTypeIdent two) { + return one.getFullyQualifiedClassName().compareTo(two.getFullyQualifiedClassName()); + } + }); + + // add the tree elements + systemOverview.addChild(getPlatformSensorClassesLeaf(platformIdent, platformSensorTypeIdentList, definition)); + systemOverview.addChild(getPlatformSensorCpuLeaf(platformIdent, platformSensorTypeIdentList, definition)); + systemOverview.addChild(getPlatformSensorMemoryLeaf(platformIdent, platformSensorTypeIdentList, definition)); + systemOverview.addChild(getPlatformSensorThreadLeaf(platformIdent, platformSensorTypeIdentList, definition)); + systemOverview.addChild(getPlatformSensorVMSummaryLeaf(platformIdent, platformSensorTypeIdentList, definition)); + + // sort the tree elements + Collections.sort(systemOverview.getChildren(), new Comparator() { + public int compare(Component componentOne, Component componentTwo) { + return componentOne.getName().compareTo(componentTwo.getName()); + } + }); + + return systemOverview; + } + + /** + * Creates the cpu leaf. + * + * @param platformIdent + * The platform ident object. + * + * @param platformSensorTypeIdents + * The list of {@link PlatformSensorTypeIdent}. + * @param definition + * The {@link RepositoryDefinition} object. + * @return An instance of {@link Component}. + */ + private Component getPlatformSensorCpuLeaf(PlatformIdent platformIdent, List platformSensorTypeIdents, RepositoryDefinition definition) { + Component cpuOverview = new Leaf(); + boolean sensorTypeAvailable = false; + cpuOverview.setName("CPU"); + cpuOverview.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_CPU_OVERVIEW)); + + for (PlatformSensorTypeIdent platformSensorTypeIdent : platformSensorTypeIdents) { + if (platformSensorTypeIdent.getFullyQualifiedClassName().equalsIgnoreCase(SensorTypeEnum.CPU_INFORMATION.getFqn())) { + sensorTypeAvailable = true; + + InputDefinition inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(definition); + inputDefinition.setId(SensorTypeEnum.CPU_INFORMATION); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SYSTEM_OVERVIEW)); + editorPropertiesData.setSensorName("System Overview"); + editorPropertiesData.setViewImage(SensorTypeEnum.CPU_INFORMATION.getImage()); + editorPropertiesData.setViewName("CPU Information"); + editorPropertiesData.setPartImageFlag(PartType.VIEW); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(platformIdent.getId()); + idDefinition.setSensorTypeId(platformSensorTypeIdent.getId()); + + inputDefinition.setIdDefinition(idDefinition); + cpuOverview.setInputDefinition(inputDefinition); + break; + } + } + + if (!sensorTypeAvailable) { + cpuOverview.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_ITEM_NA_GREY)); + cpuOverview.setTooltip(SensorTypeAvailabilityEnum.CPU_INF_NA.getMessage()); + } + + return cpuOverview; + } + + /** + * Creates the platform sensor classes leaf. + * + * @param platformIdent + * The platform ident object. + * + * @param platformSensorTypeIdents + * The list of {@link PlatformSensorTypeIdent}. + * @param definition + * The {@link RepositoryDefinition} object. + * @return An instance of {@link Component}. + */ + private Component getPlatformSensorClassesLeaf(PlatformIdent platformIdent, List platformSensorTypeIdents, RepositoryDefinition definition) { + Component classesOverview = new Leaf(); + boolean sensorTypeAvailable = false; + classesOverview.setName("Classes"); + classesOverview.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_CLASS_OVERVIEW)); + + for (PlatformSensorTypeIdent platformSensorTypeIdent : platformSensorTypeIdents) { + if (platformSensorTypeIdent.getFullyQualifiedClassName().equalsIgnoreCase(SensorTypeEnum.CLASSLOADING_INFORMATION.getFqn())) { + sensorTypeAvailable = true; + + InputDefinition inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(definition); + inputDefinition.setId(SensorTypeEnum.CLASSLOADING_INFORMATION); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SYSTEM_OVERVIEW)); + editorPropertiesData.setSensorName("System Overview"); + editorPropertiesData.setViewImage(SensorTypeEnum.CLASSLOADING_INFORMATION.getImage()); + editorPropertiesData.setViewName("Class Loading Information"); + editorPropertiesData.setPartImageFlag(PartType.VIEW); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(platformIdent.getId()); + idDefinition.setSensorTypeId(platformSensorTypeIdent.getId()); + + inputDefinition.setIdDefinition(idDefinition); + classesOverview.setInputDefinition(inputDefinition); + break; + } + } + + if (!sensorTypeAvailable) { + classesOverview.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_ITEM_NA_GREY)); + classesOverview.setTooltip(SensorTypeAvailabilityEnum.CLASS_INF_NA.getMessage()); + } + + return classesOverview; + } + + /** + * Creates the platform sensor memory leaf. + * + * @param platformIdent + * The platform ident object. + * + * @param platformSensorTypeIdents + * The list of {@link PlatformSensorTypeIdent}. + * @param definition + * The {@link RepositoryDefinition} object. + * @return An instance of {@link Component}. + */ + private Component getPlatformSensorMemoryLeaf(PlatformIdent platformIdent, List platformSensorTypeIdents, RepositoryDefinition definition) { + Component memoryOverview = new Leaf(); + boolean sensorTypeAvailable = false; + memoryOverview.setName("Memory"); + memoryOverview.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_MEMORY_OVERVIEW)); + + for (PlatformSensorTypeIdent platformSensorTypeIdent : platformSensorTypeIdents) { + if (platformSensorTypeIdent.getFullyQualifiedClassName().equalsIgnoreCase(SensorTypeEnum.MEMORY_INFORMATION.getFqn())) { + sensorTypeAvailable = true; + List platformSensorTypeIdentList = new ArrayList(); + // add sensor types to local list + platformSensorTypeIdentList.add(platformSensorTypeIdent); + for (PlatformSensorTypeIdent platformSensorTypeIdent2 : platformSensorTypeIdents) { + if (platformSensorTypeIdent2.getFullyQualifiedClassName().equalsIgnoreCase(SensorTypeEnum.SYSTEM_INFORMATION.getFqn())) { + platformSensorTypeIdentList.add(platformSensorTypeIdent2); + } + } + + InputDefinition inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(definition); + inputDefinition.setId(SensorTypeEnum.MEMORY_INFORMATION); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SYSTEM_OVERVIEW)); + editorPropertiesData.setSensorName("System Overview"); + editorPropertiesData.setViewImage(SensorTypeEnum.MEMORY_INFORMATION.getImage()); + editorPropertiesData.setViewName("Memory Information"); + editorPropertiesData.setPartImageFlag(PartType.VIEW); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(platformIdent.getId()); + idDefinition.setSensorTypeId(platformSensorTypeIdent.getId()); + + inputDefinition.setIdDefinition(idDefinition); + memoryOverview.setInputDefinition(inputDefinition); + break; + } + } + + if (!sensorTypeAvailable) { + memoryOverview.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_ITEM_NA_GREY)); + memoryOverview.setTooltip(SensorTypeAvailabilityEnum.MEMORY_INF_NA.getMessage()); + } + + return memoryOverview; + } + + /** + * Creates the platform sensor thread leaf. + * + * @param platformIdent + * The platform ident object. + * + * @param platformSensorTypeIdents + * The list of {@link PlatformSensorTypeIdent}. + * @param definition + * The {@link RepositoryDefinition} object. + * @return An instance of {@link Component}. + */ + private Component getPlatformSensorThreadLeaf(PlatformIdent platformIdent, List platformSensorTypeIdents, RepositoryDefinition definition) { + Component threadsOverview = new Leaf(); + boolean sensorTypeAvailable = false; + threadsOverview.setName("Threads"); + threadsOverview.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_THREADS_OVERVIEW)); + + for (PlatformSensorTypeIdent platformSensorTypeIdent : platformSensorTypeIdents) { + if (platformSensorTypeIdent.getFullyQualifiedClassName().equalsIgnoreCase(SensorTypeEnum.THREAD_INFORMATION.getFqn())) { + sensorTypeAvailable = true; + + InputDefinition inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(definition); + inputDefinition.setId(SensorTypeEnum.THREAD_INFORMATION); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SYSTEM_OVERVIEW)); + editorPropertiesData.setSensorName("System Overview"); + editorPropertiesData.setViewImage(SensorTypeEnum.THREAD_INFORMATION.getImage()); + editorPropertiesData.setViewName("Thread Information"); + editorPropertiesData.setPartImageFlag(PartType.VIEW); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(platformIdent.getId()); + idDefinition.setSensorTypeId(platformSensorTypeIdent.getId()); + + inputDefinition.setIdDefinition(idDefinition); + threadsOverview.setInputDefinition(inputDefinition); + break; + } + } + + if (!sensorTypeAvailable) { + threadsOverview.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_ITEM_NA_GREY)); + threadsOverview.setTooltip(SensorTypeAvailabilityEnum.THREAD_INF_NA.getMessage()); + } + + return threadsOverview; + } + + /** + * Creates the platform sensor VM Summary leaf. + * + * @param platformIdent + * The platform ident object. + * + * @param platformSensorTypeIdents + * The list of {@link PlatformSensorTypeIdent}. + * @param definition + * The {@link RepositoryDefinition} object. + * @return An instance of {@link Component}. + */ + private Component getPlatformSensorVMSummaryLeaf(PlatformIdent platformIdent, List platformSensorTypeIdents, RepositoryDefinition definition) { + Component vmSummary = new Leaf(); + boolean sensorTypeAvailable = false; + vmSummary.setName("VM Summary"); + vmSummary.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_VM_SUMMARY)); + + if (!platformSensorTypeIdents.isEmpty()) { + sensorTypeAvailable = true; + + InputDefinition inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(definition); + inputDefinition.setId(SensorTypeEnum.SYSTEM_INFORMATION); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SYSTEM_OVERVIEW)); + editorPropertiesData.setSensorName("System Overview"); + editorPropertiesData.setViewImage(InspectIT.getDefault().getImage(InspectITImages.IMG_VM_SUMMARY)); + editorPropertiesData.setViewName("VM Summary"); + editorPropertiesData.setPartImageFlag(PartType.VIEW); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(platformIdent.getId()); + + inputDefinition.setIdDefinition(idDefinition); + vmSummary.setInputDefinition(inputDefinition); + } + + if (!sensorTypeAvailable) { + vmSummary.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_ITEM_NA_GREY)); + vmSummary.setTooltip(SensorTypeAvailabilityEnum.SENSOR_NA.getMessage()); + } + + return vmSummary; + } + + /** + * Returns the exception sensor tree. + * + * @param platformIdent + * The {@link PlatformIdent} object used to create the tree. + * @param definition + * The {@link RepositoryDefinition} object. + * @return The exception sensor tree. + */ + private Component getExceptionSensorTree(PlatformIdent platformIdent, RepositoryDefinition definition) { + Composite exceptionSensor = new Composite(); + exceptionSensor.setName("Exceptions"); + exceptionSensor.setTooltip("All recorded exceptions can be analyzed here."); + + exceptionSensor.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_EXCEPTION_SENSOR)); + exceptionSensor.addChild(getUngroupedExceptionOverview(platformIdent, definition)); + exceptionSensor.addChild(getGroupedExceptionOverview(platformIdent, definition)); + + return exceptionSensor; + } + + /** + * Returns the ungrouped Exception Overview. + * + * @param platformIdent + * The {@link PlatformIdent} object. + * @param definition + * The {@link RepositoryDefinition} object. + * @return The Exception Tree. + */ + private Component getUngroupedExceptionOverview(PlatformIdent platformIdent, RepositoryDefinition definition) { + Component ungroupedExceptionOverview = new Leaf(); + ungroupedExceptionOverview.setName("Show All"); + ungroupedExceptionOverview.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SHOW_ALL)); + + InputDefinition ungroupedExceptionOverviewInputDefinition = new InputDefinition(); + ungroupedExceptionOverviewInputDefinition.setRepositoryDefinition(definition); + ungroupedExceptionOverviewInputDefinition.setId(SensorTypeEnum.EXCEPTION_SENSOR); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(SensorTypeEnum.EXCEPTION_SENSOR.getImage()); + editorPropertiesData.setSensorName("Exceptions"); + editorPropertiesData.setViewImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SHOW_ALL)); + editorPropertiesData.setViewName("Show All"); + ungroupedExceptionOverviewInputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(platformIdent.getId()); + + ungroupedExceptionOverviewInputDefinition.setIdDefinition(idDefinition); + ungroupedExceptionOverview.setInputDefinition(ungroupedExceptionOverviewInputDefinition); + + return ungroupedExceptionOverview; + } + + /** + * Returns the grouped Exception Overview. + * + * @param platformIdent + * The {@link PlatformIdent} object. + * @param definition + * The {@link RepositoryDefinition} object. + * @return The Exception Sensor overview. + */ + private Component getGroupedExceptionOverview(PlatformIdent platformIdent, RepositoryDefinition definition) { + Component groupedExceptionOverview = new Leaf(); + groupedExceptionOverview.setName("Grouped"); + groupedExceptionOverview.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_FILTER)); + + InputDefinition groupedExceptionOverviewInputDefinition = new InputDefinition(); + groupedExceptionOverviewInputDefinition.setRepositoryDefinition(definition); + groupedExceptionOverviewInputDefinition.setId(SensorTypeEnum.EXCEPTION_SENSOR_GROUPED); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(SensorTypeEnum.EXCEPTION_SENSOR_GROUPED.getImage()); + editorPropertiesData.setSensorName("Exceptions"); + editorPropertiesData.setViewImage(InspectIT.getDefault().getImage(InspectITImages.IMG_FILTER)); + editorPropertiesData.setViewName("Grouped"); + groupedExceptionOverviewInputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(platformIdent.getId()); + groupedExceptionOverviewInputDefinition.setIdDefinition(idDefinition); + groupedExceptionOverview.setInputDefinition(groupedExceptionOverviewInputDefinition); + + return groupedExceptionOverview; + } + + /** + * Returns the Timer data tree. + * + * @param platformIdent + * The platform ident used to create the tree. + * @param definition + * The {@link RepositoryDefinition} object. + * @return The timer data tree. + */ + private Component getTimerTree(PlatformIdent platformIdent, RepositoryDefinition definition) { + Composite timerDataComposite = new Composite(); + timerDataComposite.setName("Timer Data"); + timerDataComposite.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_TIMER)); + timerDataComposite.setTooltip("With these views, the timer data objects can be analyzed to identify e.g. long running methods."); + + Component showAll = new Leaf(); + showAll.setName("Show All"); + showAll.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SHOW_ALL)); + + InputDefinition inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(definition); + inputDefinition.setId(SensorTypeEnum.TIMER); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(SensorTypeEnum.TIMER.getImage()); + editorPropertiesData.setSensorName("Timer Data"); + editorPropertiesData.setViewImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SHOW_ALL)); + editorPropertiesData.setViewName("Show All"); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(platformIdent.getId()); + + inputDefinition.setIdDefinition(idDefinition); + showAll.setInputDefinition(inputDefinition); + + FilteredDeferredBrowserComposite browser = new FilteredDeferredBrowserComposite(SensorTypeEnum.TIMER); + browser.setPlatformIdent(platformIdent); + browser.setRepositoryDefinition(repositoryDefinition); + browser.setName("Browser"); + browser.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_INSTRUMENTATION_BROWSER)); + browser.setHideInactiveInstrumentations(hideInactiveInstrumentations); + + timerDataComposite.addChild(showAll); + timerDataComposite.addChild(browser); + + return timerDataComposite; + } + + /** + * Returns the Http Timer data tree. + * + * @param platformIdent + * The platform ident used to create the tree. + * @param definition + * The {@link RepositoryDefinition} object. + * @return The timer data tree. + */ + private Component getHttpTimerTree(PlatformIdent platformIdent, RepositoryDefinition definition) { + Composite timerDataComposite = new Composite(); + timerDataComposite.setName("Http Timer Data"); + timerDataComposite.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_HTTP)); + + Component urlAggregationView = new Leaf(); + urlAggregationView.setName("URI Aggregation"); + urlAggregationView.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_HTTP_URL)); + urlAggregationView.setTooltip("Aggregates all http requests that are currently in the buffer based on its URI"); + + InputDefinition inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(definition); + inputDefinition.setId(SensorTypeEnum.HTTP_TIMER_SENSOR); + + EditorPropertiesData editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(SensorTypeEnum.HTTP_TIMER_SENSOR.getImage()); + editorPropertiesData.setSensorName("Http Timer Data"); + editorPropertiesData.setViewImage(InspectIT.getDefault().getImage(InspectITImages.IMG_HTTP_URL)); + editorPropertiesData.setViewName("URI Aggregation"); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + IdDefinition idDefinition = new IdDefinition(); + idDefinition.setPlatformId(platformIdent.getId()); + for (SensorTypeIdent sensorTypeIdent : platformIdent.getSensorTypeIdents()) { + if (ObjectUtils.equals(sensorTypeIdent.getFullyQualifiedClassName(), SensorTypeEnum.HTTP_TIMER_SENSOR.getFqn())) { + idDefinition.setSensorTypeId(sensorTypeIdent.getId()); + break; + } + } + + inputDefinition.setIdDefinition(idDefinition); + urlAggregationView.setInputDefinition(inputDefinition); + + timerDataComposite.addChild(urlAggregationView); + + Component taggedView = new Leaf(); + taggedView.setName("Use Case Aggregation"); + taggedView.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_HTTP_TAGGED)); + taggedView.setTooltip("Aggregates all http request that are currently in the buffer based on a the concrete value of the inspectIT Tag Header (called \"" + + HttpTimerData.INSPECTIT_TAGGING_HEADER + "\")"); + + inputDefinition = new InputDefinition(); + inputDefinition.setRepositoryDefinition(definition); + inputDefinition.setId(SensorTypeEnum.TAGGED_HTTP_TIMER_SENSOR); + + editorPropertiesData = new EditorPropertiesData(); + editorPropertiesData.setSensorImage(SensorTypeEnum.TAGGED_HTTP_TIMER_SENSOR.getImage()); + editorPropertiesData.setSensorName("Http Timer Data"); + editorPropertiesData.setViewImage(InspectIT.getDefault().getImage(InspectITImages.IMG_HTTP_TAGGED)); + editorPropertiesData.setViewName("Use Case Aggregation"); + inputDefinition.setEditorPropertiesData(editorPropertiesData); + + idDefinition = new IdDefinition(); + idDefinition.setPlatformId(platformIdent.getId()); + + inputDefinition.setIdDefinition(idDefinition); + taggedView.setInputDefinition(inputDefinition); + + timerDataComposite.addChild(taggedView); + + return timerDataComposite; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/storage/LocalStorageLeaf.java b/inspectIT/src/info/novatec/inspectit/rcp/model/storage/LocalStorageLeaf.java new file mode 100644 index 000000000..61cc59b1b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/storage/LocalStorageLeaf.java @@ -0,0 +1,89 @@ +package info.novatec.inspectit.rcp.model.storage; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.model.Leaf; +import info.novatec.inspectit.rcp.provider.ILocalStorageDataProvider; +import info.novatec.inspectit.storage.LocalStorageData; + +import org.eclipse.core.runtime.Assert; + +import com.google.common.base.Objects; + +/** + * Leaf used for displaying the local storages in the storage tree. + * + * @author Ivan Senic + * + */ +public class LocalStorageLeaf extends Leaf implements ILocalStorageDataProvider { + + /** + * {@link LocalStorageData}. + */ + private LocalStorageData localStorageData; + + /** + * Default constructor. + * + * @param localStorageData + * {@link LocalStorageData} leaf to hold. + */ + public LocalStorageLeaf(LocalStorageData localStorageData) { + super(); + Assert.isNotNull(localStorageData); + this.localStorageData = localStorageData; + this.setName(localStorageData.getName()); + this.setTooltip(localStorageData.getName()); + this.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_STORAGE_DOWNLOADED)); + } + + /** + * Gets {@link #localStorageData}. + * + * @return {@link #localStorageData} + */ + public LocalStorageData getLocalStorageData() { + return localStorageData; + } + + /** + * Sets {@link #localStorageData}. + * + * @param localStorageData + * New value for {@link #localStorageData} + */ + public void setLocalStorageData(LocalStorageData localStorageData) { + this.localStorageData = localStorageData; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(localStorageData); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + if (!super.equals(object)) { + return false; + } + LocalStorageLeaf that = (LocalStorageLeaf) object; + return Objects.equal(this.localStorageData, that.localStorageData); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/storage/LocalStorageTreeModelManager.java b/inspectIT/src/info/novatec/inspectit/rcp/model/storage/LocalStorageTreeModelManager.java new file mode 100644 index 000000000..55bf3fa67 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/storage/LocalStorageTreeModelManager.java @@ -0,0 +1,105 @@ +package info.novatec.inspectit.rcp.model.storage; + +import info.novatec.inspectit.rcp.formatter.ImageFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.model.Composite; +import info.novatec.inspectit.rcp.model.Leaf; +import info.novatec.inspectit.storage.LocalStorageData; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.label.type.AbstractStorageLabelType; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections.CollectionUtils; + +/** + * Tree model manager for storage manager view that displays the local storage data. + * + * @author Ivan Senic + * + */ +public class LocalStorageTreeModelManager { + + /** + * Collection of {@link LocalStorageData} to be displayed in tree. + */ + private Collection localStorageDataCollection; + + /** + * Label type for grouping. + */ + private AbstractStorageLabelType storageLabelType; + + /** + * Default constructor. + * + * @param localStorageDataCollection + * Collection of {@link LocalStorageData} to be displayed in tree. + * @param storageLabelType + * Label type for grouping. + */ + public LocalStorageTreeModelManager(Collection localStorageDataCollection, AbstractStorageLabelType storageLabelType) { + super(); + this.localStorageDataCollection = localStorageDataCollection; + this.storageLabelType = storageLabelType; + } + + /** + * Returns objects divided either by the provided label class, or by + * {@link info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition} they are located to. + * + * @return Returns objects divided either by the provided label class, or by + * {@link info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition} they are + * located to. + */ + public Object[] getRootObjects() { + if (CollectionUtils.isEmpty(localStorageDataCollection)) { + return new Object[0]; + } + + if (null != storageLabelType) { + Composite unknown = new Composite(); + unknown.setName("Unknown"); + unknown.setImage(ImageFormatter.getImageForLabel(storageLabelType)); + boolean addUnknown = false; + Map map = new HashMap(); + for (LocalStorageData localStorageData : localStorageDataCollection) { + List> labelList = localStorageData.getLabels(storageLabelType); + if (CollectionUtils.isNotEmpty(labelList)) { + for (AbstractStorageLabel label : labelList) { + Composite c = map.get(TextFormatter.getLabelValue(label, true)); + if (c == null) { + c = new Composite(); + c.setName(TextFormatter.getLabelName(label) + ": " + TextFormatter.getLabelValue(label, true)); + c.setImage(ImageFormatter.getImageForLabel(storageLabelType)); + map.put(TextFormatter.getLabelValue(label, true), c); + } + LocalStorageLeaf localStorageLeaf = new LocalStorageLeaf(localStorageData); + localStorageLeaf.setParent(c); + c.addChild(localStorageLeaf); + } + } else { + unknown.addChild(new LocalStorageLeaf(localStorageData)); + addUnknown = true; + } + } + ArrayList returnList = new ArrayList(); + returnList.addAll(map.values()); + if (addUnknown) { + returnList.add(unknown); + } + return returnList.toArray(new Composite[returnList.size()]); + } else { + List leafList = new ArrayList(); + for (LocalStorageData localStorageData : localStorageDataCollection) { + leafList.add(new LocalStorageLeaf(localStorageData)); + } + return leafList.toArray(); + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/storage/StorageLeaf.java b/inspectIT/src/info/novatec/inspectit/rcp/model/storage/StorageLeaf.java new file mode 100644 index 000000000..5a2a8c47d --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/storage/StorageLeaf.java @@ -0,0 +1,93 @@ +package info.novatec.inspectit.rcp.model.storage; + +import info.novatec.inspectit.rcp.formatter.ImageFormatter; +import info.novatec.inspectit.rcp.model.Leaf; +import info.novatec.inspectit.rcp.provider.IStorageDataProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.storage.StorageData; + +import org.eclipse.core.runtime.Assert; + +import com.google.common.base.Objects; + +/** + * Leaf used for displaying the storages in the storage tree. + * + * @author Ivan Senic + * + */ +public class StorageLeaf extends Leaf implements IStorageDataProvider { + + /** + * {@link StorageData}. + */ + private StorageData storageData; + + /** + * {@link CmrRepositoryDefinition}. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * Default constructor. + * + * @param storageData + * {@link StorageData} + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + */ + public StorageLeaf(StorageData storageData, CmrRepositoryDefinition cmrRepositoryDefinition) { + super(); + Assert.isNotNull(storageData); + Assert.isNotNull(cmrRepositoryDefinition); + this.storageData = storageData; + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + this.setName(storageData.getName()); + this.setImage(ImageFormatter.getImageForStorageLeaf(storageData)); + this.setTooltip(storageData.getName()); + } + + /** + * {@inheritDoc} + */ + public StorageData getStorageData() { + return storageData; + } + + /** + * {@inheritDoc} + */ + public CmrRepositoryDefinition getCmrRepositoryDefinition() { + return cmrRepositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(storageData, cmrRepositoryDefinition); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + if (!super.equals(object)) { + return false; + } + StorageLeaf that = (StorageLeaf) object; + return Objects.equal(this.storageData, that.storageData) && Objects.equal(this.cmrRepositoryDefinition, that.cmrRepositoryDefinition); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/model/storage/StorageTreeModelManager.java b/inspectIT/src/info/novatec/inspectit/rcp/model/storage/StorageTreeModelManager.java new file mode 100644 index 000000000..dd74424b6 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/model/storage/StorageTreeModelManager.java @@ -0,0 +1,114 @@ +package info.novatec.inspectit.rcp.model.storage; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.formatter.ImageFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.model.Composite; +import info.novatec.inspectit.rcp.model.GroupedLabelsComposite; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.label.type.AbstractStorageLabelType; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections.CollectionUtils; + +/** + * Tree model manager for storage manager view. + * + * @author Ivan Senic + * + */ +public class StorageTreeModelManager { + + /** + * Storage and repository map. + */ + private Map storageRepositoryMap; + + /** + * Label type for grouping. + */ + private AbstractStorageLabelType storageLabelType; + + /** + * @param storageRepositoryMap + * map of {@link StorageData} objects and repositories where they are located. + * @param storageLabelType + * {@link AbstractStorageLabelType} to define the label ordering. It can be null, + * then Storages will be ordered by repository. + */ + public StorageTreeModelManager(Map storageRepositoryMap, AbstractStorageLabelType storageLabelType) { + super(); + this.storageRepositoryMap = storageRepositoryMap; + this.storageLabelType = storageLabelType; + } + + /** + * Returns objects divided either by the provided label class, or by + * {@link CmrRepositoryDefinition} they are located to. + * + * @return Returns objects divided either by the provided label class, or by + * {@link CmrRepositoryDefinition} they are located to. + */ + public Object[] getRootObjects() { + if (null == storageRepositoryMap || storageRepositoryMap.isEmpty()) { + return new Object[0]; + } + + if (null != storageLabelType) { + Composite unknown = new GroupedLabelsComposite(); + unknown.setName("Unknown"); + unknown.setImage(ImageFormatter.getImageForLabel(storageLabelType)); + boolean addUnknown = false; + Map map = new HashMap(); + for (Map.Entry entry : storageRepositoryMap.entrySet()) { + List> labelList = entry.getKey().getLabels(storageLabelType); + if (CollectionUtils.isNotEmpty(labelList)) { + for (AbstractStorageLabel label : labelList) { + Composite c = map.get(TextFormatter.getLabelValue(label, true)); + if (c == null) { + c = new GroupedLabelsComposite(label); + c.setName(TextFormatter.getLabelName(label) + ": " + TextFormatter.getLabelValue(label, true)); + c.setImage(ImageFormatter.getImageForLabel(storageLabelType)); + map.put(TextFormatter.getLabelValue(label, true), c); + } + StorageLeaf storageLeaf = new StorageLeaf(entry.getKey(), entry.getValue()); + storageLeaf.setParent(c); + c.addChild(storageLeaf); + } + } else { + unknown.addChild(new StorageLeaf(entry.getKey(), entry.getValue())); + addUnknown = true; + } + } + ArrayList returnList = new ArrayList(); + returnList.addAll(map.values()); + if (addUnknown) { + returnList.add(unknown); + } + return returnList.toArray(new Composite[returnList.size()]); + } else { + Map map = new HashMap(); + for (Map.Entry entry : storageRepositoryMap.entrySet()) { + CmrRepositoryDefinition cmrRepositoryDefinition = entry.getValue(); + Composite c = map.get(cmrRepositoryDefinition); + if (c == null) { + c = new Composite(); + c.setName(cmrRepositoryDefinition.getName()); + c.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_SERVER_ONLINE_SMALL)); + map.put(cmrRepositoryDefinition, c); + } + StorageLeaf storageLeaf = new StorageLeaf(entry.getKey(), entry.getValue()); + storageLeaf.setParent(c); + c.addChild(storageLeaf); + } + return map.values().toArray(new Composite[map.values().size()]); + } + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/preferences/InspectITPreferenceInitializer.java b/inspectIT/src/info/novatec/inspectit/rcp/preferences/InspectITPreferenceInitializer.java new file mode 100644 index 000000000..28d543138 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/preferences/InspectITPreferenceInitializer.java @@ -0,0 +1,52 @@ +package info.novatec.inspectit.rcp.preferences; + +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; + +/** + * Initializes the default preferences. + * + * @author Patrice Bouillet + * + */ +public class InspectITPreferenceInitializer extends AbstractPreferenceInitializer { + + /** + * {@inheritDoc} + */ + @Override + public void initializeDefaultPreferences() { + // CMR list + List defaultCmrList = new ArrayList(1); + CmrRepositoryDefinition defaultCmr = new CmrRepositoryDefinition(CmrRepositoryDefinition.DEFAULT_IP, CmrRepositoryDefinition.DEFAULT_PORT, CmrRepositoryDefinition.DEFAULT_NAME); + defaultCmr.setDescription(CmrRepositoryDefinition.DEFAULT_DESCRIPTION); + defaultCmrList.add(defaultCmr); + PreferencesUtils.saveCmrRepositoryDefinitions(defaultCmrList, true); + + // Editor defaults + PreferencesUtils.saveIntValue(PreferencesConstants.DECIMAL_PLACES, 0, true); + PreferencesUtils.saveLongValue(PreferencesConstants.REFRESH_RATE, 5000L, true); + PreferencesUtils.saveIntValue(PreferencesConstants.ITEMS_COUNT_TO_SHOW, 100, true); + PreferencesUtils.saveDoubleValue(PreferencesConstants.INVOCATION_FILTER_EXCLUSIVE_TIME, Double.NaN, true); + PreferencesUtils.saveDoubleValue(PreferencesConstants.INVOCATION_FILTER_TOTAL_TIME, Double.NaN, true); + Set> invocDataTypes = new HashSet<>(); + invocDataTypes.add(InvocationSequenceData.class); + invocDataTypes.add(TimerData.class); + invocDataTypes.add(HttpTimerData.class); + invocDataTypes.add(SqlStatementData.class); + invocDataTypes.add(ExceptionSensorData.class); + PreferencesUtils.saveObject(PreferencesConstants.INVOCATION_FILTER_DATA_TYPES, invocDataTypes, true); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/preferences/PreferenceException.java b/inspectIT/src/info/novatec/inspectit/rcp/preferences/PreferenceException.java new file mode 100644 index 000000000..d36ff5cdd --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/preferences/PreferenceException.java @@ -0,0 +1,40 @@ +package info.novatec.inspectit.rcp.preferences; + +/** + * Exception that is thrown with regard to preference saving and loading. + * + * @author Ivan Senic + * + */ +public class PreferenceException extends Exception { + + /** + * Generated GUI. + */ + private static final long serialVersionUID = 50343882577720433L; + + /** + * Default constructor. + */ + public PreferenceException() { + super(); + } + + /** + * @param message + * Message. + */ + public PreferenceException(String message) { + super(message); + } + + /** + * @param message + * Message. + * @param cause + * Cause. + */ + public PreferenceException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/preferences/PreferencesConstants.java b/inspectIT/src/info/novatec/inspectit/rcp/preferences/PreferencesConstants.java new file mode 100644 index 000000000..0aff5e18f --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/preferences/PreferencesConstants.java @@ -0,0 +1,81 @@ +package info.novatec.inspectit.rcp.preferences; + +/** + * Interface that just holds the all inspectIT preferences keys. + * + * @author Ivan Senic + * + */ +public interface PreferencesConstants { + + /** + * Token used to separate the objects when list of properties of objects that are saved. + */ + String PREF_OBJECT_SEPARATION_TOKEN = "|"; + + /** + * Split regex for creating preference string. + */ + String PREF_SPLIT_REGEX = "#"; + + /** + * Preference key for storing CMR repository definitions. + */ + String CMR_REPOSITORY_DEFINITIONS = "CMR_REPOSITORY_DEFINITIONS"; + + /** + * Preference key for columns size of our tables. + */ + String TABLE_COLUMN_SIZE_CACHE = "TABLE_COLUMN_SIZE_CACHE"; + + /** + * Preference key for hidden columns of our tables. + */ + String HIDDEN_TABLE_COLUMN_CACHE = "HIDDEN_TABLE_COLUMN_CACHE"; + + /** + * Preference key for columns order of our tables. + */ + String TABLE_COLUMN_ORDER_CACHE = "TABLE_COLUMN_ORDER_CACHE"; + + /** + * Preference key for refresh rate in the editors. + */ + String REFRESH_RATE = "REFRESH_RATE"; + + /** + * Preference key for decimal places displayed in the editors. + */ + String DECIMAL_PLACES = "DECIMAL_PLACES"; + + /** + * Items to show in editors tables. + */ + String ITEMS_COUNT_TO_SHOW = "ITEMS_COUNT_TO_SHOW"; + + /** + * Invocation filter exclusive time preference. + */ + String INVOCATION_FILTER_EXCLUSIVE_TIME = "INVOCATION_FILTER_EXCLUSIVE_TIME"; + + /** + * Invocation filter total time preference. + */ + String INVOCATION_FILTER_TOTAL_TIME = "INVOCATION_FILTER_TOTAL_TIME"; + + /** + * Invocation filter data types preference. + */ + String INVOCATION_FILTER_DATA_TYPES = "INVOCATION_FILTER_DATA_TYPES"; + + /** + * Last selected repository in data explorer view. + */ + String LAST_SELECTED_REPOSITORY = "LAST_SELECTED_REPOSITORY"; + + /** + * Last selected agent in data explorer view. + */ + String LAST_SELECTED_AGENT = "LAST_SELECTED_AGENT"; + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/preferences/PreferencesUtils.java b/inspectIT/src/info/novatec/inspectit/rcp/preferences/PreferencesUtils.java new file mode 100644 index 000000000..4f1adaff1 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/preferences/PreferencesUtils.java @@ -0,0 +1,391 @@ +package info.novatec.inspectit.rcp.preferences; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.preferences.valueproviders.PreferenceValueProviderFactory; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.eclipse.ui.preferences.ScopedPreferenceStore; + +/** + * Utility for using preferences stores. + * + * @author Ivan Senic + * + */ +public final class PreferencesUtils { + + /** + * Save the instance to the preference store. + */ + private static ScopedPreferenceStore preferenceStore = InspectIT.getDefault().getPreferenceStore(); + + /** + * Private constructor. + */ + private PreferencesUtils() { + } + + /** + * Saves a double value to the preference store. + * + * @param name + * Name of the preference. + * @param value + * Value to be saved. + * @param isDefault + * If this is true, the setting will be saved as a default preference. Not that the + * default preferences are not saved to disk, and have to be entered manually. If it + * is false, preference will be saved in the configuration scope. + */ + public static void saveDoubleValue(String name, double value, boolean isDefault) { + if (isDefault) { + preferenceStore.setDefault(name, value); + } else { + preferenceStore.setValue(name, value); + } + try { + preferenceStore.save(); + } catch (IOException e) { + InspectIT.getDefault().createErrorDialog("Error occurred trying to save setting with name '" + name + "' to preference store.", e, -1); + } + } + + /** + * Returns double value from the preferences. Same as calling + * {@link #getDoubleValue(PreferenceKey, false)}. + * + * @param name + * Name of the preference. + * @return String value. + */ + public static double getDoubleValue(String name) { + return getDoubleValue(name, false); + } + + /** + * Returns double value from the preferences. + * + * @param name + * Name of the preference. + * @param isDefault + * Should default value be retrieved. + * @return Double value. + */ + public static double getDoubleValue(String name, boolean isDefault) { + double value; + if (isDefault) { + value = preferenceStore.getDefaultDouble(name); + } else { + value = preferenceStore.getDouble(name); + } + return value; + } + + /** + * Saves a long value to the preference store. + * + * @param name + * Name of the preference. + * @param value + * Value to be saved. + * @param isDefault + * If this is true, the setting will be saved as a default preference. Not that the + * default preferences are not saved to disk, and have to be entered manually. If it + * is false, preference will be saved in the configuration scope. + */ + public static void saveLongValue(String name, long value, boolean isDefault) { + if (isDefault) { + preferenceStore.setDefault(name, value); + } else { + preferenceStore.setValue(name, value); + } + try { + preferenceStore.save(); + } catch (IOException e) { + InspectIT.getDefault().createErrorDialog("Error occurred trying to save setting with name '" + name + "' to preference store.", e, -1); + } + } + + /** + * Returns long value from the preferences. Same as calling + * {@link #getLongValue(PreferenceKey, false)}. + * + * @param name + * Name of the preference. + * @return Long value. + */ + public static long getLongValue(String name) { + return getLongValue(name, false); + } + + /** + * Returns long value from the preferences. + * + * @param name + * Name of the preference. + * @param isDefault + * Should default value be retrieved. + * @return Long value. + */ + public static long getLongValue(String name, boolean isDefault) { + long value; + if (isDefault) { + value = preferenceStore.getDefaultLong(name); + } else { + value = preferenceStore.getLong(name); + } + return value; + } + + /** + * Saves a int value to the preference store. + * + * @param name + * Name of the preference. + * @param value + * Value to be saved. + * @param isDefault + * If this is true, the setting will be saved as a default preference. Not that the + * default preferences are not saved to disk, and have to be entered manually. If it + * is false, preference will be saved in the configuration scope. + */ + public static void saveIntValue(String name, int value, boolean isDefault) { + if (isDefault) { + preferenceStore.setDefault(name, value); + } else { + preferenceStore.setValue(name, value); + } + try { + preferenceStore.save(); + } catch (IOException e) { + InspectIT.getDefault().createErrorDialog("Error occurred trying to save setting with name '" + name + "' to preference store.", e, -1); + } + } + + /** + * Returns int value from the preferences. Same as calling + * {@link #getIntValue(PreferenceKey, false)}. + * + * @param name + * Name of the preference. + * @return Int value. + */ + public static int getIntValue(String name) { + return getIntValue(name, false); + } + + /** + * Returns int value from the preferences. + * + * @param name + * Name of the preference. + * @param isDefault + * Should default value be retrieved. + * @return Int value. + */ + public static int getIntValue(String name, boolean isDefault) { + int value; + if (isDefault) { + value = preferenceStore.getDefaultInt(name); + } else { + value = preferenceStore.getInt(name); + } + return value; + } + + /** + * Saves a string value to the preference store. + * + * @param name + * Name of the preference. + * @param value + * Value to be saved. + * @param isDefault + * If this is true, the setting will be saved as a default preference. Not that the + * default preferences are not saved to disk, and have to be entered manually. If it + * is false, preference will be saved in the configuration scope. + */ + public static void saveStringValue(String name, String value, boolean isDefault) { + if (isDefault) { + preferenceStore.setDefault(name, value); + } else { + preferenceStore.setValue(name, value); + } + try { + preferenceStore.save(); + } catch (IOException e) { + InspectIT.getDefault().createErrorDialog("Error occurred trying to save setting with name '" + name + "' to preference store.", e, -1); + } + } + + /** + * Returns string value from the preferences. Same as calling + * {@link #getStringValue(PreferenceKey, false)}. + * + * @param name + * Name of the preference. + * @return String value. + */ + public static String getStringValue(String name) { + return getStringValue(name, false); + } + + /** + * Returns string value from the preferences. + * + * @param name + * Name of the preference. + * @param isDefault + * Should default value be retrieved. + * @return String value. + */ + public static String getStringValue(String name, boolean isDefault) { + String value; + if (isDefault) { + value = preferenceStore.getDefaultString(name); + } else { + value = preferenceStore.getString(name); + } + return value; + } + + /** + * General method for saving an object to the preference store. Note that the preference key has + * to be mapped in the {@link PreferenceValueProviderFactory} to the provider that can handle + * the type of object passed. + * + * @param preferenceKey + * Preference key. + * @param object + * Object to save. + * @param isDefault + * Is it a default preference value. + */ + public static void saveObject(String preferenceKey, Object object, boolean isDefault) { + try { + // if null is passed save still + if (null == object) { + saveStringValue(preferenceKey, "", isDefault); + return; + } + + String value = PreferenceValueProviderFactory.getValueForObject(preferenceKey, object); + if (value != null && !"".equals(value)) { + saveStringValue(preferenceKey, value, isDefault); + } + } catch (PreferenceException e) { + InspectIT.getDefault().createErrorDialog("Error trying to save object to the preference store with preference key: " + preferenceKey, e, -1); + } + } + + /** + * General method for loading an object from the preference store. Note that the preference key + * has to be mapped in the {@link PreferenceValueProviderFactory} to the provider that can + * handle the type of type E passed. + * + * @param + * Type of object. + * @param preferenceKey + * Preference key. + * @return Saved object or null. + */ + public static E getObject(String preferenceKey) { + try { + String value = preferenceStore.getString(preferenceKey); + if (value == null || "".equals(value)) { + return null; + } + return PreferenceValueProviderFactory.getObjectFromValue(preferenceKey, value); + } catch (Exception e) { + InspectIT.getDefault().createErrorDialog("Error trying to load object from the preference store with preference key: " + preferenceKey, e, -1); + return null; + } + } + + /** + * Loads the primitive collection from a preference store. Note that the preference key provided + * has to be mapped to the + * {@link info.novatec.inspectit.rcp.preferences.valueproviders.CollectionPreferenceValueProvider} + * in the {@link PreferenceValueProviderFactory}. + * + * @param + * Type of objects in the collection. + * @param preferenceKey + * Preference key. + * @param collection + * Collection to add objects to. Can not be null. + * @param wantedClass + * Runtime class of elements that need to be created in the collection. + */ + public static void loadPrimitiveCollection(String preferenceKey, Collection collection, Class wantedClass) { + String value = preferenceStore.getString(preferenceKey); + if (value == null || "".equals(value)) { + return; + } + try { + Collection stringCollection = PreferenceValueProviderFactory.getObjectFromValue(preferenceKey, value); + StringToPrimitiveTransformUtil.transformStringCollection(stringCollection, collection, wantedClass); + } catch (PreferenceException e) { + InspectIT.getDefault().createErrorDialog("Error trying to load primitive collection from the preference store with preference key: " + preferenceKey, e, -1); + } + } + + /** + * Loads the primitive keys and values map from a preference store. Note that the preference key + * provided has to be mapped to the + * {@link info.novatec.inspectit.rcp.preferences.valueproviders.MapPreferenceValueProvider} in + * the {@link PreferenceValueProviderFactory}. + * + * @param + * Type of key. + * @param + * Type of value. + * @param preferenceKey + * Preference key. + * @param map + * Map to put entries to. Can not be null. + * @param keyClass + * Runtime class of elements that are keys. + * @param valueClass + * Runtime class of elements that are values. + */ + public static void loadPrimitiveMap(String preferenceKey, Map map, Class keyClass, Class valueClass) { + String value = preferenceStore.getString(preferenceKey); + if (value == null || "".equals(value)) { + return; + } + try { + Map stringCollection = PreferenceValueProviderFactory.getObjectFromValue(preferenceKey, value); + StringToPrimitiveTransformUtil.transformStringMap(stringCollection, map, keyClass, valueClass); + } catch (PreferenceException e) { + InspectIT.getDefault().createErrorDialog("Error trying to load primitive map from the preference store with preference key: " + preferenceKey, e, -1); + } + } + + /** + * Save given repository definitions to the preference store. + * + * @param repositoryDefinitions + * {@link CmrRepositoryDefinition} to save. + * @param isDefault + * Is it a default setting. + */ + public static void saveCmrRepositoryDefinitions(List repositoryDefinitions, boolean isDefault) { + saveObject(PreferencesConstants.CMR_REPOSITORY_DEFINITIONS, repositoryDefinitions, isDefault); + } + + /** + * Returns the list of {@link CmrRepositoryDefinition} that exists in the preference store. + * + * @return he list of {@link CmrRepositoryDefinition} that exists in the preference store. + */ + public static List getCmrRepositoryDefinitions() { + return getObject(PreferencesConstants.CMR_REPOSITORY_DEFINITIONS); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/preferences/StringToPrimitiveTransformUtil.java b/inspectIT/src/info/novatec/inspectit/rcp/preferences/StringToPrimitiveTransformUtil.java new file mode 100644 index 000000000..624285997 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/preferences/StringToPrimitiveTransformUtil.java @@ -0,0 +1,120 @@ +package info.novatec.inspectit.rcp.preferences; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Map; + +/** + * Utilities to transform string or string collections and maps to the same with primitive wrapper + * types. + * + * @author Ivan Senic + * + */ +public final class StringToPrimitiveTransformUtil { + + /** + * Private constructor. + */ + private StringToPrimitiveTransformUtil() { + } + + /** + * Transforms all the strings in the original collection to the given class objects and adds + * them to the given resulting collection. + * + * @param + * Type of collection. + * @param original + * Source collection. + * @param collection + * Collection to add transformed elements to. + * @param elementClass + * Runtime class of objects to be added to the collection. + * @throws PreferenceException + * If transformation fails. + */ + @SuppressWarnings("unchecked") + public static void transformStringCollection(Collection original, Collection collection, Class elementClass) throws PreferenceException { + Method parseMethod = findParseMethod(elementClass); + if (null != parseMethod) { + for (String toTransform : original) { + try { + Object transformed = parseMethod.invoke(null, toTransform); + if (elementClass.isAssignableFrom(transformed.getClass())) { + collection.add((E) transformed); + } + } catch (Exception e) { + throw new PreferenceException("Error transforming Collection to Collection<" + elementClass.getName() + ">.", e); + } + } + } else { + throw new PreferenceException("Error transforming Collection to Collection<" + elementClass.getName() + ">. Parsing method can not be found in class " + + elementClass.getName() + "."); + } + } + + /** + * Transforms all the strings key/value pairs in the original map to the given class key/value + * pairs and adds them to the given resulting map. + * + * @param + * Type of key. + * @param + * Type of value. + * @param original + * Original map. + * @param map + * Map to add transformed entries to. + * @param keyClass + * Runtime class of key objects. + * @param valueClass + * Runtime class of value objects. + * @throws PreferenceException + * If transformation fails. + */ + @SuppressWarnings("unchecked") + public static void transformStringMap(Map original, Map map, Class keyClass, Class valueClass) throws PreferenceException { + Method parseKeyMethod = findParseMethod(keyClass); + Method parseValueMethod = findParseMethod(valueClass); + if (null != parseKeyMethod && null != parseValueMethod) { + for (Map.Entry toTransformEntry : original.entrySet()) { + try { + Object transformedKey = parseKeyMethod.invoke(null, toTransformEntry.getKey()); + Object transformedValue = parseValueMethod.invoke(null, toTransformEntry.getValue()); + if (keyClass.isAssignableFrom(transformedKey.getClass()) && valueClass.isAssignableFrom(transformedValue.getClass())) { + map.put((K) transformedKey, (V) transformedValue); + } + } catch (Exception e) { + throw new PreferenceException("Error transforming Map to Map<" + keyClass.getName() + ", " + valueClass.getName() + ">.", e); + } + } + } else if (null == parseKeyMethod) { + throw new PreferenceException("Error transforming Map to Map<" + keyClass.getName() + ", " + valueClass.getName() + ">." + + "Parsing method can not be found in class " + keyClass.getName() + "."); + } else { + throw new PreferenceException("Error transforming Map to Map<" + keyClass.getName() + ", " + valueClass.getName() + ">." + + "Parsing method can not be found in class " + valueClass.getName() + "."); + } + } + + /** + * Finds the parseXXX(String) method in given class if it exists. + * + * @param clazz + * Class to examine. + * @return Method or null if method can no be found. + */ + private static Method findParseMethod(Class clazz) { + Method[] methods = clazz.getMethods(); + for (Method method : methods) { + if (method.getName().startsWith("parse")) { + Class[] params = method.getParameterTypes(); + if (params.length == 1 && params[0].equals(String.class)) { + return method; + } + } + } + return null; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/preferences/page/CmrRepositoryPreferencePage.java b/inspectIT/src/info/novatec/inspectit/rcp/preferences/page/CmrRepositoryPreferencePage.java new file mode 100644 index 000000000..57d50a6f7 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/preferences/page/CmrRepositoryPreferencePage.java @@ -0,0 +1,456 @@ +package info.novatec.inspectit.rcp.preferences.page; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.dialog.AddCmrRepositoryDefinitionDialog; +import info.novatec.inspectit.rcp.preferences.PreferencesUtils; +import info.novatec.inspectit.rcp.repository.CmrRepositoryChangeListener; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.repository.CmrRepositoryManager; +import info.novatec.inspectit.rcp.wizard.ManageLabelWizard; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.preference.PreferencePage; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; + +/** + * Preference page for {@link CmrRepositoryDefinition} management. + *

+ * This class is not used at the moment. It's not confirmed that quality of the class in with the + * standards, however, it can serve as an entry to future work regarding preferences. + * + * @author Ivan Senic + * + */ +public class CmrRepositoryPreferencePage extends PreferencePage implements IWorkbenchPreferencePage, CmrRepositoryChangeListener { + + /** + * {@link CmrRepositoryManager}. + */ + private CmrRepositoryManager cmrRepositoryManager; + + /** + * Table with repositories. + */ + private TableViewer tableViewer; + + /** + * Add button. + */ + private Button addButton; + + /** + * Remove button. + */ + private Button removeButton; + + /** + * Refresh button. + */ + private Button refreshButton; + + /** + * Manage labels button. + */ + private Button manageLabelsButton; + + /** + * Input list to save changes until Apply or OK are fired. + */ + private Map inputList; + + /** + * Default constructor. + */ + public CmrRepositoryPreferencePage() { + } + + /** + * Sec. constructor. + * + * @param title + * Title of preference page. + */ + public CmrRepositoryPreferencePage(String title) { + super(title); + } + + /** + * Third constructor. + * + * @param title + * Title of preference page. + * @param image + * Image. + */ + public CmrRepositoryPreferencePage(String title, ImageDescriptor image) { + super(title, image); + } + + /** + * {@inheritDoc} + */ + @Override + public void init(IWorkbench workbench) { + cmrRepositoryManager = InspectIT.getDefault().getCmrRepositoryManager(); + cmrRepositoryManager.addCmrRepositoryChangeListener(this); + inputList = new ConcurrentHashMap(); + for (CmrRepositoryDefinition cmrRepositoryDefinition : cmrRepositoryManager.getCmrRepositoryDefinitions()) { + inputList.put(cmrRepositoryDefinition, cmrRepositoryDefinition.getOnlineStatus()); + } + noDefaultAndApplyButton(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Control createContents(Composite parent) { + Composite mainComposite = new Composite(parent, SWT.INHERIT_DEFAULT); + GridLayout layout = new GridLayout(2, false); + layout.marginHeight = 0; + layout.marginWidth = 0; + mainComposite.setLayout(layout); + + Label info = new Label(mainComposite, SWT.NONE); + info.setText("Add, remove and manage repositories"); + GridData labelGridData = new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false); + labelGridData.horizontalSpan = 2; + info.setLayoutData(labelGridData); + + Table table = new Table(mainComposite, SWT.MULTI | SWT.FULL_SELECTION | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.VIRTUAL); + table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + table.setHeaderVisible(true); + table.setLinesVisible(true); + + tableViewer = new TableViewer(table); + createColumns(); + tableViewer.setContentProvider(new ArrayContentProvider()); + tableViewer.setInput(inputList.keySet()); + + tableViewer.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + updateButtonsState(); + } + }); + + Composite buttonComposite = new Composite(mainComposite, SWT.INHERIT_DEFAULT); + GridLayout buttonLayout = new GridLayout(1, true); + buttonLayout.marginHeight = 0; + buttonLayout.marginWidth = 0; + buttonComposite.setLayout(buttonLayout); + buttonComposite.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false)); + + addButton = new Button(buttonComposite, SWT.PUSH); + addButton.setText("Add"); + addButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + addButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + AddCmrRepositoryDefinitionDialog dialog = new AddCmrRepositoryDefinitionDialog(getShell()); + dialog.open(); + if (dialog.getReturnCode() == Dialog.OK && null != dialog.getCmrRepositoryDefinition()) { + inputList.put(dialog.getCmrRepositoryDefinition(), OnlineStatus.OFFLINE); + cmrRepositoryManager.forceCmrRepositoryOnlineStatusUpdate(dialog.getCmrRepositoryDefinition()); + tableViewer.refresh(); + } + } + }); + + removeButton = new Button(buttonComposite, SWT.PUSH); + removeButton.setText("Remove"); + removeButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + removeButton.setEnabled(false); + removeButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + StructuredSelection selection = (StructuredSelection) tableViewer.getSelection(); + for (Object selectedObject : selection.toArray()) { + if (selectedObject instanceof CmrRepositoryDefinition) { + inputList.remove((CmrRepositoryDefinition) selectedObject); + } + } + tableViewer.refresh(); + } + }); + + refreshButton = new Button(buttonComposite, SWT.PUSH); + refreshButton.setText("Refresh"); + refreshButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + refreshButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + for (CmrRepositoryDefinition cmrRepositoryDefinition : inputList.keySet()) { + cmrRepositoryManager.forceCmrRepositoryOnlineStatusUpdate(cmrRepositoryDefinition); + } + } + }); + + manageLabelsButton = new Button(buttonComposite, SWT.PUSH); + manageLabelsButton.setText("Manage Labels"); + manageLabelsButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + manageLabelsButton.setEnabled(false); + manageLabelsButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + StructuredSelection selection = (StructuredSelection) tableViewer.getSelection(); + for (Object selectedObject : selection.toArray()) { + if (selectedObject instanceof CmrRepositoryDefinition) { + ManageLabelWizard mlw = new ManageLabelWizard((CmrRepositoryDefinition) selectedObject); + WizardDialog wizardDialog = new WizardDialog(getShell(), mlw); + wizardDialog.open(); + } + } + } + }); + + return mainComposite; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean performOk() { + saveChanges(); + return super.performOk(); + } + + @Override + public void dispose() { + super.dispose(); + cmrRepositoryManager.removeCmrRepositoryChangeListener(this); + } + + /** + * {@inheritDoc} + *

+ * Does nothing. + */ + public void repositoryAdded(CmrRepositoryDefinition repositoryDefinition) { + } + + /** + * {@inheritDoc} + *

+ * Does nothing. + */ + public void repositoryRemoved(CmrRepositoryDefinition repositoryDefinition) { + } + + /** + * {@inheritDoc} + */ + public void repositoryOnlineStatusUpdated(final CmrRepositoryDefinition repositoryDefinition, OnlineStatus oldStatus, OnlineStatus newStatus) { + if (newStatus != OnlineStatus.CHECKING && inputList.containsKey(repositoryDefinition)) { + OnlineStatus oldRegisteredStatus = inputList.get(repositoryDefinition); + if (!oldRegisteredStatus.equals(newStatus)) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + tableViewer.refresh(repositoryDefinition); + updateButtonsState(); + } + }); + inputList.put(repositoryDefinition, newStatus); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryDataUpdated(final CmrRepositoryDefinition cmrRepositoryDefinition) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + tableViewer.refresh(cmrRepositoryDefinition); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryAgentDeleted(CmrRepositoryDefinition cmrRepositoryDefinition, PlatformIdent agent) { + } + + /** + * Updates the state of the remove and license info buttons depending on the current table + * selection. + */ + private void updateButtonsState() { + StructuredSelection structuredSelection = (StructuredSelection) tableViewer.getSelection(); + if (structuredSelection.isEmpty()) { + removeButton.setEnabled(false); + manageLabelsButton.setEnabled(false); + } else { + removeButton.setEnabled(true); + if (structuredSelection.size() == 1 && ((CmrRepositoryDefinition) structuredSelection.getFirstElement()).getOnlineStatus() == OnlineStatus.ONLINE) { + manageLabelsButton.setEnabled(true); + } else { + manageLabelsButton.setEnabled(false); + } + } + } + + /** + * Save made changes. + */ + private void saveChanges() { + // do nothing if no changes are there + if (!isDirty()) { + return; + } + + // add all new + for (CmrRepositoryDefinition cmrRepositoryDefinition : inputList.keySet()) { + if (!cmrRepositoryManager.getCmrRepositoryDefinitions().contains(cmrRepositoryDefinition)) { + cmrRepositoryManager.addCmrRepositoryDefinition(cmrRepositoryDefinition); + } + } + + // remove all deleted + List removeList = new ArrayList(); + for (CmrRepositoryDefinition cmrRepositoryDefinition : cmrRepositoryManager.getCmrRepositoryDefinitions()) { + if (!inputList.keySet().contains(cmrRepositoryDefinition)) { + removeList.add(cmrRepositoryDefinition); + } + } + if (!removeList.isEmpty()) { + for (CmrRepositoryDefinition cmrRepositoryDefinition : removeList) { + cmrRepositoryManager.removeCmrRepositoryDefinition(cmrRepositoryDefinition); + } + } + + // save to local preferences + savePreferences(); + } + + /** + * Where there changes performed by user. + * + * @return Where there changes performed by user. + */ + private boolean isDirty() { + return !Objects.equals(inputList.keySet(), cmrRepositoryManager.getCmrRepositoryDefinitions()); + } + + /** + * Saves the changes to preference store. + */ + private void savePreferences() { + PreferencesUtils.saveCmrRepositoryDefinitions(cmrRepositoryManager.getCmrRepositoryDefinitions(), false); + } + + /** + * Creates columns for the table. + */ + private void createColumns() { + TableViewerColumn onlineColumn = new TableViewerColumn(tableViewer, SWT.NONE); + onlineColumn.getColumn().setResizable(false); + onlineColumn.getColumn().setWidth(24); + onlineColumn.setLabelProvider(new ColumnLabelProvider() { + + @Override + public Image getImage(Object element) { + if (element instanceof CmrRepositoryDefinition) { + if (((CmrRepositoryDefinition) element).getOnlineStatus() == OnlineStatus.ONLINE) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_SERVER_ONLINE_SMALL); + } else if (((CmrRepositoryDefinition) element).getOnlineStatus() == OnlineStatus.OFFLINE) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_SERVER_OFFLINE_SMALL); + } else { + return InspectIT.getDefault().getImage(InspectITImages.IMG_SERVER_REFRESH_SMALL); + } + } + return null; + } + + @Override + public String getText(Object element) { + return null; + } + + }); + + TableViewerColumn nameColumn = new TableViewerColumn(tableViewer, SWT.NONE); + nameColumn.getColumn().setResizable(true); + nameColumn.getColumn().setWidth(150); + nameColumn.getColumn().setText("Name"); + nameColumn.setLabelProvider(new ColumnLabelProvider() { + + @Override + public String getText(Object element) { + if (element instanceof CmrRepositoryDefinition) { + return ((CmrRepositoryDefinition) element).getName(); + } + return null; + } + }); + + TableViewerColumn ipColumn = new TableViewerColumn(tableViewer, SWT.NONE); + ipColumn.getColumn().setResizable(true); + ipColumn.getColumn().setWidth(120); + ipColumn.getColumn().setText("IP Address"); + ipColumn.setLabelProvider(new ColumnLabelProvider() { + + @Override + public String getText(Object element) { + if (element instanceof CmrRepositoryDefinition) { + return ((CmrRepositoryDefinition) element).getIp(); + } + return null; + } + }); + + TableViewerColumn portColumn = new TableViewerColumn(tableViewer, SWT.NONE); + portColumn.getColumn().setResizable(true); + portColumn.getColumn().setWidth(50); + portColumn.getColumn().setText("Port"); + portColumn.setLabelProvider(new ColumnLabelProvider() { + + @Override + public String getText(Object element) { + if (element instanceof CmrRepositoryDefinition) { + return String.valueOf(((CmrRepositoryDefinition) element).getPort()); + } + return null; + } + }); + + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/ClassCollectionPreferenceValueProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/ClassCollectionPreferenceValueProvider.java new file mode 100644 index 000000000..41b9f6204 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/ClassCollectionPreferenceValueProvider.java @@ -0,0 +1,45 @@ +package info.novatec.inspectit.rcp.preferences.valueproviders; + +import java.util.Collection; +import java.util.HashSet; + +/** + * Extension of the {@link CollectionPreferenceValueProvider} that can save classes. + * + * @author Ivan Senic + * + */ +public class ClassCollectionPreferenceValueProvider extends CollectionPreferenceValueProvider { + + /** + * {@inheritDoc} + */ + @Override + protected Collection getCollectionForResults() { + return new HashSet<>(); + } + + /** + * {@inheritDoc} + */ + @Override + protected String getValueForCollectionMember(Object object) { + if (object instanceof Class) { + return ((Class) object).getName(); + } + return super.getValueForCollectionMember(object); + } + + /** + * {@inheritDoc} + */ + @Override + protected Object getObjectForCollectionMember(String value) { + try { + return Class.forName(value); + } catch (ClassNotFoundException e) { + return super.getValueForCollectionMember(value); + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/CmrRepositoryPreferenceValueProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/CmrRepositoryPreferenceValueProvider.java new file mode 100644 index 000000000..1946a3755 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/CmrRepositoryPreferenceValueProvider.java @@ -0,0 +1,104 @@ +package info.novatec.inspectit.rcp.preferences.valueproviders; + +import info.novatec.inspectit.rcp.preferences.PreferenceException; +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; +import info.novatec.inspectit.rcp.preferences.valueproviders.PreferenceValueProviderFactory.PreferenceValueProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.StringTokenizer; + +import org.apache.commons.collections.CollectionUtils; + +/** + * Value provider for list of {@link CmrRepositoryDefinition}s. + * + * @author Ivan Senic + * + */ +class CmrRepositoryPreferenceValueProvider extends PreferenceValueProvider> { + + /** + * Constant for denoting the empty list. + */ + private static final String EMPTY_LIST = "EMPTY_LIST"; + + /** + * {@inheritDoc} + */ + public boolean isObjectValid(Object object) { + if (object instanceof List) { + List list = (List) object; + for (Object objectInCollection : list) { + if (!objectInCollection.getClass().equals(CmrRepositoryDefinition.class)) { + return false; + } + } + return true; + } + return false; + } + + /** + * {@inheritDoc} + */ + public String getValueForObject(List repositoryDefinitions) { + if (CollectionUtils.isEmpty(repositoryDefinitions)) { + return EMPTY_LIST; + } else { + StringBuilder value = new StringBuilder(); + for (CmrRepositoryDefinition cmrRepositoryDefinition : repositoryDefinitions) { + value.append(cmrRepositoryDefinition.getIp() + PreferencesConstants.PREF_SPLIT_REGEX + cmrRepositoryDefinition.getPort() + PreferencesConstants.PREF_SPLIT_REGEX + + cmrRepositoryDefinition.getName() + PreferencesConstants.PREF_SPLIT_REGEX); + if (null != cmrRepositoryDefinition.getDescription()) { + value.append(cmrRepositoryDefinition.getDescription()); + } + value.append(PreferencesConstants.PREF_OBJECT_SEPARATION_TOKEN); + } + return value.toString(); + } + } + + /** + * {@inheritDoc} + */ + public List getObjectFromValue(String value) throws PreferenceException { + if (EMPTY_LIST.equals(value)) { + return Collections.emptyList(); + } else { + List returnList = new ArrayList(); + StringTokenizer tokenizer = new StringTokenizer(value, PreferencesConstants.PREF_OBJECT_SEPARATION_TOKEN); + while (tokenizer.hasMoreTokens()) { + String nextValue = tokenizer.nextToken(); + String[] splitted = nextValue.split(PreferencesConstants.PREF_SPLIT_REGEX); + CmrRepositoryDefinition cmrRepositoryDefinition = null; + if (splitted.length == 2) { + try { + cmrRepositoryDefinition = new CmrRepositoryDefinition(splitted[0], Integer.parseInt(splitted[1])); + } catch (Exception e) { + throw new PreferenceException("Error trying to create a CMR repository definition from preference store.", e); + } + } else if (splitted.length > 2) { + try { + cmrRepositoryDefinition = new CmrRepositoryDefinition(splitted[0], Integer.parseInt(splitted[1]), splitted[2]); + } catch (Exception e) { + throw new PreferenceException("Error trying to create a CMR repository definition from preference store.", e); + } + } else { + throw new PreferenceException("CMR repository definition values saved in the preference store are not correct. Received values via the string '" + nextValue + "' are " + + Arrays.asList(splitted) + ". Definition will be skipped."); + } + + if (null != cmrRepositoryDefinition && splitted.length > 3) { + cmrRepositoryDefinition.setDescription(splitted[3]); + } + returnList.add(cmrRepositoryDefinition); + } + return returnList; + } + } + +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/CollectionPreferenceValueProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/CollectionPreferenceValueProvider.java new file mode 100644 index 000000000..bb62af42c --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/CollectionPreferenceValueProvider.java @@ -0,0 +1,101 @@ +package info.novatec.inspectit.rcp.preferences.valueproviders; + +import info.novatec.inspectit.rcp.preferences.PreferenceException; +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; +import info.novatec.inspectit.rcp.preferences.valueproviders.PreferenceValueProviderFactory.PreferenceValueProvider; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.StringTokenizer; + +import org.apache.commons.collections.CollectionUtils; + +/** + * This {@link PreferenceValueProvider} converts any collection that members in primitive warper + * types to preference value. Later on this preference value will be transformed to a collection of + * strings, and thus needs transformation to initial class of collection members. + * + * @author Ivan Senic + * + */ +public class CollectionPreferenceValueProvider extends PreferenceValueProvider> { + + /** + * Constant for denoting the empty collection. + */ + private static final String EMPTY_COLLECTION = "EMPTY_COLLECTION"; + + /** + * {@inheritDoc} + */ + public boolean isObjectValid(Object object) { + return object instanceof Collection; + } + + /** + * {@inheritDoc} + */ + public String getValueForObject(Collection collection) throws PreferenceException { + if (CollectionUtils.isEmpty(collection)) { + return EMPTY_COLLECTION; + } else { + StringBuilder stringBuilder = new StringBuilder(); + for (Object object : collection) { + stringBuilder.append(getValueForCollectionMember(object) + PreferencesConstants.PREF_OBJECT_SEPARATION_TOKEN); + } + return stringBuilder.toString(); + } + } + + /** + * {@inheritDoc} + */ + public Collection getObjectFromValue(String value) throws PreferenceException { + if (EMPTY_COLLECTION.equals(value)) { + return getCollectionForResults(); + } else { + Collection results = getCollectionForResults(); + StringTokenizer tokenizer = new StringTokenizer(value, PreferencesConstants.PREF_OBJECT_SEPARATION_TOKEN); + while (tokenizer.hasMoreElements()) { + results.add(getObjectForCollectionMember(tokenizer.nextToken())); + } + return results; + } + } + + /** + * Returns Collection type to use when creating resulting collection from strings. + * + * @return Returns Collection type to use when creating resulting collection from strings. + */ + protected Collection getCollectionForResults() { + return new ArrayList(); + } + + /** + * Returns String value for collection member. + *

+ * Sub-classes can override. + * + * @param object + * Member. + * @return String value. + */ + protected String getValueForCollectionMember(Object object) { + return object.toString(); + } + + /** + * Returns collection member for saved String value. + *

+ * Sub-classes can override. + * + * @param value + * String value as object was saved.. + * @return Collection member.. + */ + protected Object getObjectForCollectionMember(String value) { + return value; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/ColumnOrderPreferenceValueProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/ColumnOrderPreferenceValueProvider.java new file mode 100644 index 000000000..91b97fb2b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/ColumnOrderPreferenceValueProvider.java @@ -0,0 +1,77 @@ +package info.novatec.inspectit.rcp.preferences.valueproviders; + +import info.novatec.inspectit.rcp.preferences.PreferenceException; +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; +import info.novatec.inspectit.rcp.preferences.valueproviders.PreferenceValueProviderFactory.PreferenceValueProvider; + +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; + +/** + * Custom {@link PreferenceValueProvider} for the map of column orders in the tables. + * + * @author Ivan Senic + * + */ +public class ColumnOrderPreferenceValueProvider extends PreferenceValueProvider> { + + /** + * {@inheritDoc} + */ + @Override + public boolean isObjectValid(Object object) { + return object instanceof Map; + } + + /** + * {@inheritDoc} + */ + @Override + public String getValueForObject(Map map) throws PreferenceException { + StringBuilder stringBuilder = new StringBuilder(); + for (Map.Entry entry : map.entrySet()) { + stringBuilder.append(String.valueOf(entry.getKey())); + stringBuilder.append(PreferencesConstants.PREF_SPLIT_REGEX); + for (int i : entry.getValue()) { + stringBuilder.append(String.valueOf(i)); + stringBuilder.append(PreferencesConstants.PREF_SPLIT_REGEX); + } + stringBuilder.append(PreferencesConstants.PREF_OBJECT_SEPARATION_TOKEN); + } + return stringBuilder.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public Map getObjectFromValue(String value) throws PreferenceException { + Map map = new HashMap(); + StringTokenizer tokenizer = new StringTokenizer(value, PreferencesConstants.PREF_OBJECT_SEPARATION_TOKEN); + while (tokenizer.hasMoreElements()) { + String nextEntry = tokenizer.nextToken(); + String[] splitted = nextEntry.split(PreferencesConstants.PREF_SPLIT_REGEX); + + Integer key = null; + try { + key = Integer.valueOf(splitted[0]); + } catch (Exception e) { + throw new PreferenceException("Key value of the saved column order preference could not be loaded.", e); + } + + int[] valueArray = new int[splitted.length - 1]; + for (int i = 1; i < splitted.length; i++) { + try { + valueArray[i - 1] = Integer.parseInt(splitted[i]); + } catch (Exception e) { + throw new PreferenceException("Value array of the saved column order preference could not be loaded.", e); + } + } + + map.put(key, valueArray); + } + return map; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/EnumSetPreferenceValueProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/EnumSetPreferenceValueProvider.java new file mode 100644 index 000000000..33affeb6c --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/EnumSetPreferenceValueProvider.java @@ -0,0 +1,75 @@ +package info.novatec.inspectit.rcp.preferences.valueproviders; + +import info.novatec.inspectit.rcp.preferences.PreferenceException; +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; +import info.novatec.inspectit.rcp.preferences.valueproviders.PreferenceValueProviderFactory.PreferenceValueProvider; + +import java.util.HashSet; +import java.util.Set; +import java.util.StringTokenizer; + +/** + * Provider that can save and retrieve set of enum values. + * + * @author Ivan Senic + * + * @param + */ +public class EnumSetPreferenceValueProvider> extends PreferenceValueProvider> { + + /** + * Class of the enum. + */ + private Class enumClass; + + /** + * @param enumClass + * Class of the enum. + */ + public EnumSetPreferenceValueProvider(Class enumClass) { + this.enumClass = enumClass; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isObjectValid(Object object) { + if (object instanceof Set) { + Set set = (Set) object; + for (Object inSet : set) { + if (!enumClass.isAssignableFrom(inSet.getClass())) { + return false; + } + } + return true; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public String getValueForObject(Set collection) throws PreferenceException { + StringBuilder stringBuilder = new StringBuilder(); + for (E object : collection) { + stringBuilder.append(object.toString() + PreferencesConstants.PREF_OBJECT_SEPARATION_TOKEN); + } + return stringBuilder.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public Set getObjectFromValue(String value) throws PreferenceException { + Set results = new HashSet(); + StringTokenizer tokenizer = new StringTokenizer(value, PreferencesConstants.PREF_OBJECT_SEPARATION_TOKEN); + while (tokenizer.hasMoreElements()) { + results.add(Enum.valueOf(enumClass, tokenizer.nextToken())); + } + return results; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/LastSelectedRepositoryPreferenceValueProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/LastSelectedRepositoryPreferenceValueProvider.java new file mode 100644 index 000000000..eacf40fd1 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/LastSelectedRepositoryPreferenceValueProvider.java @@ -0,0 +1,92 @@ +package info.novatec.inspectit.rcp.preferences.valueproviders; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.preferences.PreferenceException; +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; +import info.novatec.inspectit.rcp.preferences.valueproviders.PreferenceValueProviderFactory.PreferenceValueProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; +import info.novatec.inspectit.rcp.repository.StorageRepositoryDefinition; +import info.novatec.inspectit.storage.LocalStorageData; + +import java.util.Objects; + +/** + * Preference value provider that tries to save and load the last selected repository. + * + * @author Ivan Senic + * + */ +public class LastSelectedRepositoryPreferenceValueProvider extends PreferenceValueProvider { + + /** + * {@inheritDoc} + */ + @Override + public boolean isObjectValid(Object object) { + return object instanceof RepositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public String getValueForObject(RepositoryDefinition repositoryDefinition) throws PreferenceException { + StringBuilder stringBuilder = new StringBuilder(); + if (repositoryDefinition instanceof CmrRepositoryDefinition) { + stringBuilder.append(CmrRepositoryDefinition.class.getName()); + stringBuilder.append(PreferencesConstants.PREF_SPLIT_REGEX); + stringBuilder.append(((CmrRepositoryDefinition) repositoryDefinition).getIp()); + stringBuilder.append(PreferencesConstants.PREF_SPLIT_REGEX); + stringBuilder.append(((CmrRepositoryDefinition) repositoryDefinition).getPort()); + stringBuilder.append(PreferencesConstants.PREF_SPLIT_REGEX); + } else if (repositoryDefinition instanceof StorageRepositoryDefinition) { + stringBuilder.append(StorageRepositoryDefinition.class.getName()); + stringBuilder.append(PreferencesConstants.PREF_SPLIT_REGEX); + stringBuilder.append(((StorageRepositoryDefinition) repositoryDefinition).getLocalStorageData().getId()); + stringBuilder.append(PreferencesConstants.PREF_SPLIT_REGEX); + } + return stringBuilder.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public RepositoryDefinition getObjectFromValue(String value) throws PreferenceException { + String[] splitted = value.split(PreferencesConstants.PREF_SPLIT_REGEX); + if (splitted.length > 0) { + String repositoryType = splitted[0]; + if (Objects.equals(repositoryType, CmrRepositoryDefinition.class.getName())) { + if (splitted.length == 3) { + String ip = splitted[1]; + String port = splitted[2]; + for (CmrRepositoryDefinition cmrRepositoryDefinition : InspectIT.getDefault().getCmrRepositoryManager().getCmrRepositoryDefinitions()) { + if (Objects.equals(cmrRepositoryDefinition.getIp(), ip) && Objects.equals(String.valueOf(cmrRepositoryDefinition.getPort()), port)) { + return cmrRepositoryDefinition; + } + } + } else { + throw new PreferenceException("Error trying to create last selected repository and agent from preference store."); + } + } else if (Objects.equals(repositoryType, StorageRepositoryDefinition.class.getName())) { + if (splitted.length == 2) { + String storageId = splitted[1]; + for (LocalStorageData localStorageData : InspectIT.getDefault().getInspectITStorageManager().getMountedAvailableStorages()) { + if (Objects.equals(localStorageData.getId(), storageId)) { + try { + return InspectIT.getDefault().getInspectITStorageManager().getStorageRepositoryDefinition(localStorageData); + } catch (Exception e) { + throw new PreferenceException("Error trying to create a Storage repository definition from preference store.", e); + } + } + } + } else { + throw new PreferenceException("Error trying to create last selected repository and agent from preference store."); + } + } + } + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/MapPreferenceValueProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/MapPreferenceValueProvider.java new file mode 100644 index 000000000..f24cafb6a --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/MapPreferenceValueProvider.java @@ -0,0 +1,79 @@ +package info.novatec.inspectit.rcp.preferences.valueproviders; + +import info.novatec.inspectit.rcp.preferences.PreferenceException; +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; +import info.novatec.inspectit.rcp.preferences.valueproviders.PreferenceValueProviderFactory.PreferenceValueProvider; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; + +import org.apache.commons.collections.MapUtils; + +/** + * This {@link PreferenceValueProvider} converts any map that has keys and values in primitive + * warper types to preference value. Later on this preference value will be transformed to a map + * that has both string as key and value, and thus needs transformation to initial classes of keys + * and values. + * + * @author Ivan Senic + * + */ +public class MapPreferenceValueProvider extends PreferenceValueProvider> { + + /** + * Constant for denoting the empty map. + */ + private static final String EMPTY_MAP = "EMPTY_MAP"; + + /** + * {@inheritDoc} + */ + public boolean isObjectValid(Object object) { + return object instanceof Map; + } + + /** + * {@inheritDoc} + */ + public String getValueForObject(Map map) throws PreferenceException { + if (MapUtils.isEmpty(map)) { + return EMPTY_MAP; + } else { + StringBuilder stringBuilder = new StringBuilder(); + for (Map.Entry entry : map.entrySet()) { + stringBuilder.append(String.valueOf(entry.getKey())); + stringBuilder.append(PreferencesConstants.PREF_SPLIT_REGEX); + stringBuilder.append(entry.getValue()); + stringBuilder.append(PreferencesConstants.PREF_OBJECT_SEPARATION_TOKEN); + } + return stringBuilder.toString(); + } + } + + /** + * {@inheritDoc} + */ + public Map getObjectFromValue(String value) throws PreferenceException { + if (EMPTY_MAP.equals(value)) { + return Collections.emptyMap(); + } else { + Map map = new HashMap(); + StringTokenizer tokenizer = new StringTokenizer(value, PreferencesConstants.PREF_OBJECT_SEPARATION_TOKEN); + while (tokenizer.hasMoreElements()) { + String nextEntry = tokenizer.nextToken(); + String[] splitted = nextEntry.split(PreferencesConstants.PREF_SPLIT_REGEX); + if (splitted.length == 2) { + map.put(splitted[0], splitted[1]); + } else { + throw new PreferenceException("Error loading map entry for the map saved in the preference store are not correct. Entry key and value received values via the string '" + + nextEntry + "' are " + Arrays.asList(splitted) + ". Definition will be skipped."); + } + } + return map; + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/PreferenceValueProviderFactory.java b/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/PreferenceValueProviderFactory.java new file mode 100644 index 000000000..1364a4375 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/preferences/valueproviders/PreferenceValueProviderFactory.java @@ -0,0 +1,132 @@ +package info.novatec.inspectit.rcp.preferences.valueproviders; + +import info.novatec.inspectit.rcp.preferences.PreferenceException; +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; + +import java.util.HashMap; +import java.util.Map; + +/** + * Factory for providing the preference strings for objects and vice verse. + * + * @author Ivan Senic + * + */ +public final class PreferenceValueProviderFactory { + + /** + * Private constructor. + */ + private PreferenceValueProviderFactory() { + } + + /** + * List of active providers. + */ + private static Map> preferenceValueProviders; + + static { + preferenceValueProviders = new HashMap>(); + preferenceValueProviders.put(PreferencesConstants.CMR_REPOSITORY_DEFINITIONS, new CmrRepositoryPreferenceValueProvider()); + preferenceValueProviders.put(PreferencesConstants.TABLE_COLUMN_SIZE_CACHE, new MapPreferenceValueProvider()); + preferenceValueProviders.put(PreferencesConstants.HIDDEN_TABLE_COLUMN_CACHE, new CollectionPreferenceValueProvider()); + preferenceValueProviders.put(PreferencesConstants.TABLE_COLUMN_ORDER_CACHE, new ColumnOrderPreferenceValueProvider()); + preferenceValueProviders.put(PreferencesConstants.LAST_SELECTED_REPOSITORY, new LastSelectedRepositoryPreferenceValueProvider()); + preferenceValueProviders.put(PreferencesConstants.INVOCATION_FILTER_DATA_TYPES, new ClassCollectionPreferenceValueProvider()); + } + + /** + * Returns a String preference value for a given preference key and object. Note that key + * provided has to match with the {@link PreferenceValueProvider} key that works with the same + * object types as provided E type. + * + * @param + * Type of object. + * @param preferenceKey + * Preference key. + * @param object + * Object to create preference value for. + * @return String. + * @throws PreferenceException + * If exception occurs during execution. + */ + @SuppressWarnings("unchecked") + public static String getValueForObject(String preferenceKey, E object) throws PreferenceException { + PreferenceValueProvider preferenceValueProvider = preferenceValueProviders.get(preferenceKey); + if (null != preferenceValueProvider) { + if (preferenceValueProvider.isObjectValid(object)) { + return ((PreferenceValueProvider) preferenceValueProvider).getValueForObject(object); + } else { + throw new PreferenceException("Preference value for key " + preferenceKey + " could not be obtained because the supplied object is not valid."); + } + } else { + throw new PreferenceException("Preference value provider was not found for preference key: " + preferenceKey); + } + } + + /** + * Returns a object from a string preference value for a given preference key. Note that key + * provided has to match with the {@link PreferenceValueProvider} key that works with the same + * object types as provided E type. + * + * @param + * Type of object. + * @param preferenceKey + * Preference key. + * @param value + * String preference value. + * @return Object. + * @throws PreferenceException + * If exception occurs during execution. + */ + @SuppressWarnings("unchecked") + public static E getObjectFromValue(String preferenceKey, String value) throws PreferenceException { + PreferenceValueProvider preferenceValueProvider = preferenceValueProviders.get(preferenceKey); + if (null != preferenceValueProvider) { + return ((PreferenceValueProvider) preferenceValueProvider).getObjectFromValue(value); + } else { + throw new PreferenceException("Preference value provider was not found for preference key: " + preferenceKey); + } + } + + /** + * Abstract class for preference value providers. + * + * @author Ivan Senic + * + * @param + * Type that is provider working with. + */ + abstract static class PreferenceValueProvider { + + /** + * @param object + * Object to check. + * @return Returns the provider can return a preference value for the object. + */ + public abstract boolean isObjectValid(Object object); + + /** + * Returns a String for the object. + * + * @param object + * Object. + * @return String to save. + * @throws PreferenceException + * If exception occurs during execution. + */ + public abstract String getValueForObject(E object) throws PreferenceException; + + /** + * Returns a object from String. + * + * @param value + * Previously saved string. + * @return Object of type + * @throws PreferenceException + * If exception occurs during execution. + */ + public abstract E getObjectFromValue(String value) throws PreferenceException; + + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/property/CmrConfigurationDialog.java b/inspectIT/src/info/novatec/inspectit/rcp/property/CmrConfigurationDialog.java new file mode 100644 index 000000000..9d3dce552 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/property/CmrConfigurationDialog.java @@ -0,0 +1,282 @@ +package info.novatec.inspectit.rcp.property; + +import info.novatec.inspectit.cmr.property.configuration.AbstractProperty; +import info.novatec.inspectit.cmr.property.configuration.GroupedProperty; +import info.novatec.inspectit.cmr.property.configuration.PropertySection; +import info.novatec.inspectit.cmr.property.configuration.SingleProperty; +import info.novatec.inspectit.cmr.property.update.IPropertyUpdate; +import info.novatec.inspectit.cmr.property.update.configuration.ConfigurationUpdate; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.ArrayUtils; +import org.eclipse.jface.preference.IPreferenceNode; +import org.eclipse.jface.preference.IPreferencePage; +import org.eclipse.jface.preference.PreferenceManager; +import org.eclipse.jface.preference.PreferenceNode; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.internal.dialogs.FilteredPreferenceDialog; +import org.eclipse.ui.internal.dialogs.PreferenceNodeFilter; + +/** + * Dialog for displaying the CMR configuration. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("restriction") +public class CmrConfigurationDialog extends FilteredPreferenceDialog { + + /** + * CMR to configure. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * Configuration update that will be available after OK has been clicked if any updates were + * made. + */ + private ConfigurationUpdate configurationUpdate; + + /** + * If updates defined in the dialog need a server restart. + */ + private boolean serverRestartRequired; + + /** + * Filter for the advanced pages. + */ + private ViewerFilter preferenceNodeFilter = new PreferenceNodeFilter(new String[0]); + + /** + * Default constructor. + * + * @param parentShell + * Shell to use. + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to create dialog for. + */ + public CmrConfigurationDialog(Shell parentShell, CmrRepositoryDefinition cmrRepositoryDefinition) { + super(parentShell, getPreferenceManager(cmrRepositoryDefinition)); + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + protected void configureShell(Shell shell) { + super.configureShell(shell); + shell.setText("Configure CMR '" + cmrRepositoryDefinition.getName() + "' (" + cmrRepositoryDefinition.getIp() + ":" + cmrRepositoryDefinition.getPort() + ")"); + } + + /** + * {@inheritDoc} + */ + @Override + protected Control createButtonBar(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(2, false); + layout.marginWidth = 0; + layout.marginHeight = 0; + layout.horizontalSpacing = 0; + composite.setLayout(layout); + composite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); + + final Button showAdv = new Button(composite, SWT.CHECK); + showAdv.setText("Show advanced properties"); + showAdv.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, false)); + showAdv.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + showAdvanced(showAdv.getSelection()); + } + }); + + super.createButtonBar(composite); + + // executed to handle hiding of pages with only advanced properties + filteredTree.getViewer().setExpandPreCheckFilters(true); + showAdvanced(false); + + return composite; + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + protected void okPressed() { + Set> propertyUpdates = new HashSet<>(); + Iterator nodes = getPreferenceManager().getElements(PreferenceManager.PRE_ORDER).iterator(); + while (nodes.hasNext()) { + IPreferenceNode node = nodes.next(); + IPreferencePage page = node.getPage(); + if (page instanceof PropertyPreferencePage) { + PropertyPreferencePage propertyPreferencePage = (PropertyPreferencePage) page; + propertyUpdates.addAll(propertyPreferencePage.getPropertyUpdates()); + if (propertyPreferencePage.isServerRestartRequired()) { + serverRestartRequired = true; + } + } + } + + if (CollectionUtils.isNotEmpty(propertyUpdates)) { + configurationUpdate = new ConfigurationUpdate(); + configurationUpdate.setPropertyUpdates(propertyUpdates); + } + + super.okPressed(); + } + + /** + * Show/hide advanced properties on the pages. + * + * @param show + * True if advanced should be shown, false otherwise. + */ + @SuppressWarnings("unchecked") + private void showAdvanced(boolean show) { + List idsToShow = new ArrayList<>(); + Iterator nodes = getPreferenceManager().getElements(PreferenceManager.PRE_ORDER).iterator(); + while (nodes.hasNext()) { + IPreferenceNode node = nodes.next(); + IPreferencePage page = node.getPage(); + if (page instanceof PropertyPreferencePage) { + PropertyPreferencePage preferencePage = (PropertyPreferencePage) page; + preferencePage.showAdvanced(show); + if (show || showIfNoAdvanced(node)) { + idsToShow.add(node.getId()); + } + } + } + + if (null != preferenceNodeFilter) { + filteredTree.getViewer().removeFilter(preferenceNodeFilter); + } + preferenceNodeFilter = new PreferenceNodeFilter(idsToShow.toArray(new String[idsToShow.size()])); + filteredTree.getViewer().addFilter(preferenceNodeFilter); + + // switch to another page if currently displayed has only advanced properties + Collections.sort(idsToShow); + PropertyPreferencePage currentPage = (PropertyPreferencePage) getCurrentPage(); + if (null != currentPage) { + if (!show && currentPage.isAllAdvancedProperties()) { + // display first page + setCurrentPageId(idsToShow.get(0)); + } + } + } + + /** + * Defines if node should be displayed if no advanced is selected. This method is recursive. + * + * @param node + * Node to check. + * @return true this node should be displayed if only normal properties are + * defined. + */ + private boolean showIfNoAdvanced(IPreferenceNode node) { + if (node.getPage() instanceof PropertyPreferencePage) { + if (!((PropertyPreferencePage) node.getPage()).isAllAdvancedProperties()) { + return true; + } + } + + if (ArrayUtils.isNotEmpty(node.getSubNodes())) { + for (IPreferenceNode subNode : node.getSubNodes()) { + if (showIfNoAdvanced(subNode)) { + return true; + } + } + } + return false; + } + + /** + * Gets {@link #configurationUpdate}. Note that this method will return null if no + * updates were created in the dialog. + * + * @return {@link #configurationUpdate} + */ + public ConfigurationUpdate getConfigurationUpdate() { + return configurationUpdate; + } + + /** + * Gets {@link #serverRestartRequired}. + * + * @return {@link #serverRestartRequired} + */ + public boolean isServerRestartRequired() { + return serverRestartRequired; + } + + /** + * Creates preference manager for the given {@link CmrRepositoryDefinition}. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + * @return {@link PreferenceManager}. + * @throw {@link IllegalArgumentException} If given CMR is null, off-line or can + * not provide active properties. + */ + public static PreferenceManager getPreferenceManager(CmrRepositoryDefinition cmrRepositoryDefinition) { + if (null == cmrRepositoryDefinition) { + throw new IllegalArgumentException("Can not create Preference Manager because repository is null."); + } else if (cmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.OFFLINE) { + throw new IllegalArgumentException("Can not create Preference Manager because repository is off-line."); + } + + Collection sections = cmrRepositoryDefinition.getCmrManagementService().getConfigurationPropertySections(); + if (CollectionUtils.isEmpty(sections)) { + throw new IllegalArgumentException("Can not create CMR Preference Manager because repository can not provide active properties."); + } + + PreferenceManager preferenceManager = new PreferenceManager(); + for (PropertySection section : sections) { + List subNodes = new ArrayList<>(); + List> singleProperties = new ArrayList<>(); + for (AbstractProperty property : section.getProperties()) { + if (property instanceof GroupedProperty) { + GroupedProperty groupedProperty = (GroupedProperty) property; + GroupedPropertyPreferencePage preferencePage = new GroupedPropertyPreferencePage(groupedProperty); + PreferenceNode preferenceNode = new PreferenceNode(groupedProperty.getName(), preferencePage); + subNodes.add(preferenceNode); + } else if (property instanceof SingleProperty) { + singleProperties.add((SingleProperty) property); + } + } + + PropertyPreferencePage preferencePage = new PropertyPreferencePage(section.getName(), singleProperties); + PreferenceNode preferenceNode = new PreferenceNode(section.getName(), preferencePage); + preferenceManager.addToRoot(preferenceNode); + + for (IPreferenceNode subNode : subNodes) { + preferenceManager.addTo(section.getName(), subNode); + } + } + + return preferenceManager; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/property/GroupedPropertyPreferencePage.java b/inspectIT/src/info/novatec/inspectit/rcp/property/GroupedPropertyPreferencePage.java new file mode 100644 index 000000000..2aa5d9fe0 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/property/GroupedPropertyPreferencePage.java @@ -0,0 +1,111 @@ +package info.novatec.inspectit.rcp.property; + +import info.novatec.inspectit.cmr.property.configuration.GroupedProperty; +import info.novatec.inspectit.cmr.property.configuration.SingleProperty; +import info.novatec.inspectit.cmr.property.configuration.validation.PropertyValidation; +import info.novatec.inspectit.cmr.property.configuration.validation.PropertyValidationException; +import info.novatec.inspectit.cmr.property.configuration.validation.ValidationError; +import info.novatec.inspectit.cmr.property.update.IPropertyUpdate; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Special {@link PropertyPreferencePage} that can handle the {@link GroupedProperty}. + * + * @author Ivan Senic + * + */ +public class GroupedPropertyPreferencePage extends PropertyPreferencePage { + + /** + * {@link GroupedProperty} to display. + */ + private GroupedProperty groupedProperty; + + /** + * Validation of complete grouped property. + */ + private PropertyValidation propertyValidation; + + /** + * Default constructor. + * + * @param groupedProperty + * {@link GroupedProperty} to display. + * @see PropertyPreferencePage#PropertyPreferencePage(String, java.util.Collection) + */ + public GroupedPropertyPreferencePage(GroupedProperty groupedProperty) { + super(groupedProperty.getName(), groupedProperty.getSingleProperties()); + this.setDescription(groupedProperty.getDescription()); + this.groupedProperty = groupedProperty; + } + + /** + * {@inheritDoc} + */ + @Override + public void propertyUpdated(SingleProperty property, IPropertyUpdate propertyUpdate) { + super.propertyUpdated(property, propertyUpdate); + executeGroupValidation(); + updatePage(); + } + + /** + * {@inheritDoc} + */ + @Override + public void propertyUpdateCanceled(SingleProperty property) { + super.propertyUpdateCanceled(property); + executeGroupValidation(); + updatePage(); + } + + /** + * {@inheritDoc} + */ + @Override + public void propertyValidationFailed(SingleProperty property, PropertyValidation propertyValidation) { + super.propertyValidationFailed(property, propertyValidation); + executeGroupValidation(); + updatePage(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getValidationErrorsCount() { + int count = super.getValidationErrorsCount(); + if (null != propertyValidation) { + count += propertyValidation.getErrorCount(); + } + return count; + } + + /** + * {@inheritDoc} + */ + @Override + public Collection getValidationErrors() { + List errors = new ArrayList<>(); + errors.addAll(super.getValidationErrors()); + if (null != propertyValidation) { + errors.addAll(propertyValidation.getErrors()); + } + return errors; + } + + /** + * Executes group validation. + */ + private void executeGroupValidation() { + try { + groupedProperty.validateForPropertiesUpdate(correctUpdateMap.values()); + propertyValidation = null; // NOPMD + } catch (PropertyValidationException exception) { + propertyValidation = exception.getPropertyValidation(); + } + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/property/IPropertyUpdateListener.java b/inspectIT/src/info/novatec/inspectit/rcp/property/IPropertyUpdateListener.java new file mode 100644 index 000000000..57e755880 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/property/IPropertyUpdateListener.java @@ -0,0 +1,42 @@ +package info.novatec.inspectit.rcp.property; + +import info.novatec.inspectit.cmr.property.configuration.SingleProperty; +import info.novatec.inspectit.cmr.property.configuration.validation.PropertyValidation; +import info.novatec.inspectit.cmr.property.update.IPropertyUpdate; + +/** + * Update listener that property controls will report the updates. + * + * @author Ivan Senic + * + */ +public interface IPropertyUpdateListener { + + /** + * Signals that the property has been updated. + * + * @param property + * {@link SingleProperty} + * @param propertyUpdate + * {@link IPropertyUpdate} + */ + void propertyUpdated(SingleProperty property, IPropertyUpdate propertyUpdate); + + /** + * Signals that the update has been canceled. + * + * @param property + * {@link SingleProperty} + */ + void propertyUpdateCanceled(SingleProperty property); + + /** + * Signals that the property validation failed. + * + * @param property + * {@link SingleProperty} + * @param propertyValidation + * {@link PropertyValidation}. + */ + void propertyValidationFailed(SingleProperty property, PropertyValidation propertyValidation); +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/property/PropertyPreferencePage.java b/inspectIT/src/info/novatec/inspectit/rcp/property/PropertyPreferencePage.java new file mode 100644 index 000000000..89cf7e1d1 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/property/PropertyPreferencePage.java @@ -0,0 +1,378 @@ +package info.novatec.inspectit.rcp.property; + +import info.novatec.inspectit.cmr.property.configuration.PropertySection; +import info.novatec.inspectit.cmr.property.configuration.SingleProperty; +import info.novatec.inspectit.cmr.property.configuration.validation.PropertyValidation; +import info.novatec.inspectit.cmr.property.configuration.validation.ValidationError; +import info.novatec.inspectit.cmr.property.update.AbstractPropertyUpdate; +import info.novatec.inspectit.cmr.property.update.IPropertyUpdate; +import info.novatec.inspectit.rcp.property.control.AbstractPropertyControl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.preference.PreferencePage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.forms.widgets.FormText; + +/** + * Preference page for displaying the CMR properties. + * + * @author Ivan Senic + * + */ +public class PropertyPreferencePage extends PreferencePage implements IPropertyUpdateListener { + + /** + * {@link Comparator} to sort properties by being advanced or not. + */ + private static final Comparator> PROPERTIES_COMPARATOR = new Comparator>() { + + /** + * {@inheritDoc} + */ + @Override + public int compare(SingleProperty p1, SingleProperty p2) { + if (p1.isAdvanced() && !p2.isAdvanced()) { + return 1; + } else if (p1.isAdvanced() && !p2.isAdvanced()) { + return -1; + } else { + return 0; + } + } + }; + + /** + * {@link PropertySection} this page will display. + */ + private List> properties; + + /** + * All created property controls in this page. + */ + private Collection> propertyControls = new ArrayList<>(); + + /** + * Update map containing all correctly updated properties and their + * {@link AbstractPropertyUpdate}. + */ + protected Map, IPropertyUpdate> correctUpdateMap = new HashMap<>(); + + /** + * Map of the current validation problems on this page. + */ + private Map, PropertyValidation> validationMap = new HashMap, PropertyValidation>(); + + /** + * If the page contains all advanced properties. + */ + private boolean allAdvancedProperties = true; + + /** + * If advanced properties are currently visible. + */ + private boolean advancedVisible; + + /** + * Button for restoring defaults. + */ + private Button restoreDefaults; + + /** + * Main composite. + */ + private Composite mainComposite; + + /** + * For displaying the advanced properties text. + */ + private FormText advancedText; + + /** + * Default constructor. + * + * @param name + * Name of the page. + * @param properties + * Collection of properties to display. + */ + public PropertyPreferencePage(String name, Collection> properties) { + super(name); + this.properties = new ArrayList<>(properties); + noDefaultAndApplyButton(); + for (SingleProperty property : this.properties) { + if (!property.isAdvanced()) { + allAdvancedProperties = false; + } + } + Collections.sort(this.properties, PROPERTIES_COMPARATOR); + } + + /** + * {@inheritDoc} + */ + @Override + public void propertyUpdated(SingleProperty property, IPropertyUpdate propertyUpdate) { + validationMap.remove(property); + if (!propertyUpdate.isRestoreDefault() || !property.isDefaultValueUsed()) { + correctUpdateMap.put(property, propertyUpdate); + } else { + correctUpdateMap.remove(property); + } + updatePage(); + } + + /** + * {@inheritDoc} + */ + @Override + public void propertyUpdateCanceled(SingleProperty property) { + validationMap.remove(property); + correctUpdateMap.remove(property); + updatePage(); + } + + /** + * {@inheritDoc} + */ + @Override + public void propertyValidationFailed(SingleProperty property, PropertyValidation propertyValidation) { + if (propertyValidation.hasErrors()) { + validationMap.put(property, propertyValidation); + correctUpdateMap.remove(property); + } + updatePage(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isValid() { + return getValidationErrorsCount() == 0; + } + + /** + * Returns all valid property updates on this page. Note that this method will return empty + * collection if the page is not valid. + * + * @return Returns all valid property updates on this page. + */ + public Collection> getPropertyUpdates() { + if (isValid()) { + return correctUpdateMap.values(); + } else { + return Collections.emptySet(); + } + } + + /** + * If updates made on this page require the server restart. + * + * @return True if any correct update on this page requires the server restart + */ + public boolean isServerRestartRequired() { + if (isValid()) { + for (SingleProperty property : correctUpdateMap.keySet()) { + if (property.isServerRestartRequired()) { + return true; + } + } + return false; + } else { + return false; + } + } + + /** + * Updates the page valid status, message and error table if needed. + */ + protected void updatePage() { + setValid(isValid()); + updateMessage(); + updateValidationMessages(); + } + + /** + * Returns current count of validation errors. Sub-class can override this method to provide + * additional errors in the count. + * + * @return Returns current count of validation errors on this page. + */ + public int getValidationErrorsCount() { + if (MapUtils.isEmpty(validationMap)) { + return 0; + } else { + int count = 0; + for (PropertyValidation propertyValidation : validationMap.values()) { + count += propertyValidation.getErrorCount(); + } + return count; + } + } + + /** + * Returns all validation errors. Sub-class can override this method to provide additional + * errors. + * + * @return Returns current validation errors on this page. + */ + public Collection getValidationErrors() { + if (MapUtils.isEmpty(validationMap)) { + return Collections.emptyList(); + } else { + List returnList = new ArrayList<>(); + for (PropertyValidation propertyValidation : validationMap.values()) { + returnList.addAll(propertyValidation.getErrors()); + } + return returnList; + } + } + + /** + * Shows/hides advanced properties and it's controls. + * + * @param advanced + * True if advanced should be shown, false otherwise. + */ + public void showAdvanced(boolean advanced) { + advancedVisible = advanced; + + if (null != advancedText) { + advancedText.setVisible(advanced); + } + if (CollectionUtils.isNotEmpty(propertyControls)) { + for (AbstractPropertyControl propertyControl : propertyControls) { + propertyControl.showIfAdvanced(advanced); + } + + mainComposite.layout(); + mainComposite.update(); + if (allAdvancedProperties && !advancedVisible) { + setMessage("This page contains only advanced properties.", INFORMATION); + } else { + updateMessage(); + } + } + } + + /** + * Updates the message of the page based on number of validation errors. + */ + private void updateMessage() { + int count = getValidationErrorsCount(); + if (0 == count) { + setMessage(null); + } else { + String msg = count + " validation error" + (count > 1 ? "s" : "") + " detected"; + setMessage(msg, ERROR); + } + } + + /** + * Updates the error composite. + */ + private void updateValidationMessages() { + if (!mainComposite.isDisposed()) { + for (AbstractPropertyControl propertyControl : propertyControls) { + propertyControl.displayValidationErrors(getValidationErrors()); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void performDefaults() { + for (AbstractPropertyControl control : propertyControls) { + control.restoreDefault(); + } + } + + /** + * {@inheritDoc} + *

+ * We hook here the show advanced and our own restore defaults button. + */ + @Override + protected void contributeButtons(Composite parent) { + ((GridLayout) parent.getLayout()).numColumns++; + restoreDefaults = new Button(parent, SWT.PUSH); + restoreDefaults.setText("Restore Defaults"); + restoreDefaults.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + performDefaults(); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + protected Control createContents(Composite parent) { + mainComposite = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(4, false); + layout.marginHeight = 0; + layout.marginWidth = 0; + mainComposite.setLayout(layout); + + boolean advancedCreated = false; + + for (SingleProperty property : properties) { + if (!advancedCreated && property.isAdvanced()) { + advancedText = new FormText(mainComposite, SWT.WRAP); + advancedText.setText("

Advanced properties:

", true, false); + advancedText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 4, 1)); + advancedCreated = true; + } + createSinglePropertyContents(property, mainComposite); + } + + Dialog.applyDialogFont(mainComposite); + showAdvanced(advancedVisible); + mainComposite.layout(); + return mainComposite; + } + + /** + * Creates one line of widgets in the page for displaying a single property. + * + * @param property + * {@link SingleProperty} to create content for. + * @param parent + * Composite parent + */ + private void createSinglePropertyContents(SingleProperty property, Composite parent) { + AbstractPropertyControl propertyControl = AbstractPropertyControl.createFor(property, this); + propertyControl.create(parent); + propertyControls.add(propertyControl); + } + + /** + * Gets {@link #allAdvancedProperties}. + * + * @return {@link #allAdvancedProperties} + */ + public boolean isAllAdvancedProperties() { + return allAdvancedProperties; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/property/control/AbstractPropertyControl.java b/inspectIT/src/info/novatec/inspectit/rcp/property/control/AbstractPropertyControl.java new file mode 100644 index 000000000..e96f75ab8 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/property/control/AbstractPropertyControl.java @@ -0,0 +1,267 @@ +package info.novatec.inspectit.rcp.property.control; + +import info.novatec.inspectit.cmr.property.configuration.SingleProperty; +import info.novatec.inspectit.cmr.property.configuration.impl.BooleanProperty; +import info.novatec.inspectit.cmr.property.configuration.impl.ByteProperty; +import info.novatec.inspectit.cmr.property.configuration.impl.LongProperty; +import info.novatec.inspectit.cmr.property.configuration.impl.PercentageProperty; +import info.novatec.inspectit.cmr.property.configuration.impl.StringProperty; +import info.novatec.inspectit.cmr.property.configuration.validation.PropertyValidation; +import info.novatec.inspectit.cmr.property.configuration.validation.PropertyValidationException; +import info.novatec.inspectit.cmr.property.configuration.validation.ValidationError; +import info.novatec.inspectit.cmr.property.update.IPropertyUpdate; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.property.IPropertyUpdateListener; +import info.novatec.inspectit.rcp.property.control.impl.BooleanPropertyControl; +import info.novatec.inspectit.rcp.property.control.impl.BytePropertyControl; +import info.novatec.inspectit.rcp.property.control.impl.LongPropertyControl; +import info.novatec.inspectit.rcp.property.control.impl.PercentagePropertyControl; +import info.novatec.inspectit.rcp.property.control.impl.StringPropertyControl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.jface.fieldassist.ControlDecoration; +import org.eclipse.jface.fieldassist.FieldDecorationRegistry; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; + +/** + * Abstract class for all property controls. + * + * @author Ivan Senic + * + * @param + * Type of the property control can handle. + * @param + * Value holding the property. + */ +public abstract class AbstractPropertyControl, V> { + + /** + * Property. + */ + protected E property; + + /** + * If property gets changed, we will keep here the propertyUpdate. + */ + protected IPropertyUpdate propertyUpdate; + + /** + * Property update listener to report updates to. + */ + protected IPropertyUpdateListener propertyUpdateListener; + + /** + * Decoration for the mail control for displaying validation errors. + */ + protected ControlDecoration decoration; + + /** + * If decoration should be displayed. + */ + private boolean decorationDisplayed = false; + + /** + * Main control that displays the value and enables updates. + */ + private Control control; + + /** + * All controls, needed for hiding in case of advanced properties. + */ + private List allControls = new ArrayList<>(); + + /** + * Default constructor. + * + * @param property + * Property. + * @param propertyUpdateListener + * Property update listener to report updates to. + */ + protected AbstractPropertyControl(E property, IPropertyUpdateListener propertyUpdateListener) { + this.property = property; + this.propertyUpdateListener = propertyUpdateListener; + } + + /** + * Create control that will be displayed for the property. + * + * @param parent + * Parent composite. + * @return Created control. + */ + protected abstract Control createControl(Composite parent); + + /** + * Shows default value in the control. + */ + protected abstract void showDefaultValue(); + + /** + * Shows or hides all controls for this property if property is advanced. + * + * @param visible + * true if control should be visible, false otherwise. + */ + public void showIfAdvanced(boolean visible) { + if (property.isAdvanced()) { + for (Control control : allControls) { + control.setVisible(visible); + } + if (decorationDisplayed && visible) { + decoration.show(); + } else { + decoration.hide(); + } + } + } + + /** + * Restores default value in this property control. + */ + public void restoreDefault() { + this.showDefaultValue(); + propertyUpdate = property.createRestoreDefaultPropertyUpdate(); + propertyUpdateListener.propertyUpdated(property, propertyUpdate); + } + + /** + * Displays validation errors in the decoration box if any of the passed {@link ValidationError} + * s is involving the property displayed in this control. Otherwise the decoration box is + * hidden. + * + * @param errors + * Validation errors to check. + */ + public void displayValidationErrors(Collection errors) { + boolean showDecoration = false; + StringBuilder stringBuilder = new StringBuilder("Validation errors:"); + for (ValidationError error : errors) { + if (error.getInvolvedProperties().contains(property)) { + showDecoration = true; + stringBuilder.append("\n - "); + stringBuilder.append(error.getMessage()); + } + } + + if (showDecoration) { + decoration.show(); + decoration.setDescriptionText(stringBuilder.toString()); + } else { + decoration.hide(); + } + decorationDisplayed = showDecoration; + } + + /** + * Creates set of controls that will be displayed for the property. + *

+ * It is expected that the parent composite has a grid layout with four columns. + * + * @param parent + * Parent composite. + */ + public synchronized void create(Composite parent) { + if (null == control) { + // name first + Label name = new Label(parent, SWT.LEFT); + name.setText(property.getName() + ":"); + name.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); + + // then specific control + control = createControl(parent); + GridData controlGridData = new GridData(SWT.FILL, SWT.CENTER, true, false); + controlGridData.widthHint = 175; + controlGridData.horizontalIndent = 10; + control.setLayoutData(controlGridData); + + // decoration for handling validation errors + decoration = new ControlDecoration(control, SWT.LEFT | SWT.BOTTOM); + decoration.setImage(FieldDecorationRegistry.getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_ERROR).getImage()); + decoration.hide(); + + // info icon + Label info = new Label(parent, SWT.CENTER); + info.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_INFORMATION)); + info.setToolTipText(property.getDescription()); + info.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false)); + + // server re-start icon if needed + Label serverRestart = new Label(parent, SWT.CENTER); + if (property.isServerRestartRequired()) { + serverRestart.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_WARNING)); + serverRestart.setToolTipText("Changing this property requires restart of the CMR"); + } + serverRestart.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false)); + + CollectionUtils.addAll(allControls, new Object[] { name, control, info, serverRestart }); + } + } + + /** + * Sends property update event. + * + * @param newValue + * New value. + */ + protected void sendPropertyUpdateEvent(V newValue) { + if (!property.getValue().equals(newValue)) { + try { + propertyUpdate = property.createAndValidatePropertyUpdate(newValue); + propertyUpdateListener.propertyUpdated(property, propertyUpdate); + } catch (PropertyValidationException e) { + PropertyValidation propertyValidation = e.getPropertyValidation(); + propertyUpdateListener.propertyValidationFailed(property, propertyValidation); + } + } else { + propertyUpdateListener.propertyUpdateCanceled(property); + propertyUpdate = null; // NOPMD + } + + } + + /** + * @return Returns the last correctly set value for the property. + */ + protected V getLastCorrectValue() { + if (null != propertyUpdate) { + return propertyUpdate.getUpdateValue(); + } else { + return property.getValue(); + } + } + + /** + * Utility method for creating the {@link AbstractPropertyControl}. + * + * @param property + * Property to get the control for. + * @param propertyUpdateListener + * Property update listener to report updates to. + * @return Returns {@link AbstractPropertyControl} or null if the one for the given + * property can not be created. + */ + public static AbstractPropertyControl createFor(SingleProperty property, IPropertyUpdateListener propertyUpdateListener) { + if (property instanceof BooleanProperty) { + return new BooleanPropertyControl((BooleanProperty) property, propertyUpdateListener); + } else if (property instanceof PercentageProperty) { + return new PercentagePropertyControl((PercentageProperty) property, propertyUpdateListener); + } else if (property instanceof StringProperty) { + return new StringPropertyControl((StringProperty) property, propertyUpdateListener); + } else if (property instanceof LongProperty) { + return new LongPropertyControl((LongProperty) property, propertyUpdateListener); + } else if (property instanceof ByteProperty) { + return new BytePropertyControl((ByteProperty) property, propertyUpdateListener); + } + return null; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/BooleanPropertyControl.java b/inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/BooleanPropertyControl.java new file mode 100644 index 000000000..6ad967c2b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/BooleanPropertyControl.java @@ -0,0 +1,79 @@ +package info.novatec.inspectit.rcp.property.control.impl; + +import info.novatec.inspectit.cmr.property.configuration.impl.BooleanProperty; +import info.novatec.inspectit.rcp.property.IPropertyUpdateListener; +import info.novatec.inspectit.rcp.property.control.AbstractPropertyControl; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** + * {@link AbstractPropertyControl} for the boolean property. + * + * @author Ivan Senic + * + */ +public class BooleanPropertyControl extends AbstractPropertyControl { + + /** + * Button for on/off. + */ + private Button button; + + /** + * Default constructor. + * + * @param property + * Property. + * @param propertyUpdateListener + * Property update listener to report updates to. + */ + public BooleanPropertyControl(BooleanProperty property, IPropertyUpdateListener propertyUpdateListener) { + super(property, propertyUpdateListener); + } + + /** + * {@inheritDoc} + */ + @Override + public Control createControl(Composite parent) { + button = new Button(parent, SWT.TOGGLE); + button.setSelection(property.getValue().booleanValue()); + updateText(); + + button.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + updateText(); + sendPropertyUpdateEvent(Boolean.valueOf(button.getSelection())); + } + }); + + return button; + } + + /** + * {@inheritDoc} + */ + @Override + protected void showDefaultValue() { + button.setSelection(property.getDefaultValue().booleanValue()); + updateText(); + } + + /** + * Updates the text in the button. + */ + private void updateText() { + if (button.getSelection()) { + button.setText("On"); + } else { + button.setText("Off"); + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/BytePropertyControl.java b/inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/BytePropertyControl.java new file mode 100644 index 000000000..b3f8dcfeb --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/BytePropertyControl.java @@ -0,0 +1,202 @@ +package info.novatec.inspectit.rcp.property.control.impl; + +import info.novatec.inspectit.cmr.property.configuration.impl.ByteProperty; +import info.novatec.inspectit.rcp.property.IPropertyUpdateListener; +import info.novatec.inspectit.rcp.property.control.AbstractPropertyControl; + +import java.util.Locale; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.events.VerifyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Text; + +/** + * Property control for {@link ByteProperty}. + * + * @author Ivan Senic + * + */ +public class BytePropertyControl extends AbstractPropertyControl { + + /** + * Combo displaying the available units. + */ + private Combo unitCombo; + + /** + * Value being displayed. + */ + private Text valueText; + + /** + * Flag to skip the modify listener when {@link #valueText} is changed by us. + */ + private boolean modifyMarker; + + /** + * Default constructor. + * + * @param property + * Property. + * @param propertyUpdateListener + * Property update listener to report updates to. + */ + public BytePropertyControl(ByteProperty property, IPropertyUpdateListener propertyUpdateListener) { + super(property, propertyUpdateListener); + } + + /** + * {@inheritDoc} + */ + @Override + protected Control createControl(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + GridLayout gridLayout = new GridLayout(2, false); + gridLayout.marginHeight = 0; + gridLayout.marginWidth = 0; + composite.setLayout(gridLayout); + + valueText = new Text(composite, SWT.BORDER | SWT.RIGHT); + valueText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + valueText.addVerifyListener(new VerifyListener() { + @Override + public void verifyText(VerifyEvent e) { + String oldText = valueText.getText(); + String update = e.text; + String newText = oldText.substring(0, e.start) + update + oldText.substring(e.end, oldText.length()); + + // allow blank text + if (StringUtils.isNotBlank(newText)) { + // otherwise prove we have a valid double number + try { + Double.parseDouble(newText); + } catch (NumberFormatException exception) { + e.doit = false; + return; + } + } + } + }); + valueText.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + if (!modifyMarker) { + String text = valueText.getText(); + if (!text.isEmpty() && (text.charAt(0) != '-' || text.length() > 1)) { + long currentSize = getCurrentSize(); + sendPropertyUpdateEvent(currentSize); + } + } else { + modifyMarker = false; + } + } + }); + + valueText.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + String text = valueText.getText(); + if (text.isEmpty()) { + displayValue(getLastCorrectValue()); + } + } + }); + + unitCombo = new Combo(composite, SWT.BORDER); + unitCombo.setItems(new String[] { "B", "KB", "MB", "GB" }); + GridData unitGd = new GridData(SWT.RIGHT, SWT.FILL, false, false); + unitGd.widthHint = 60; + unitCombo.setLayoutData(unitGd); + unitCombo.addSelectionListener(new SelectionAdapter() { + + public void widgetSelected(SelectionEvent e) { + long currentSize = getLastCorrectValue(); + int exp = unitCombo.getSelectionIndex(); + displayValue(currentSize, exp); + }; + }); + + displayValue(property.getValue().longValue()); + + return composite; + } + + /** + * {@inheritDoc} + */ + @Override + protected void showDefaultValue() { + displayValue(property.getDefaultValue().longValue()); + } + + /** + * @return Returns current size defined by spinner and unit. + */ + private long getCurrentSize() { + return (long) (Double.parseDouble(valueText.getText()) * Math.pow(1024, unitCombo.getSelectionIndex())); + } + + /** + * Displays value in text based on default unit . + * + * @param bytes + * Amount of bytes. + */ + private void displayValue(long bytes) { + int exp = getUnit(bytes); + unitCombo.select(exp); + displayValue(bytes, exp); + } + + /** + * Displays value in text based on given exp. + * + * @param bytes + * Amount of bytes. + * @param exp + * Unit index or exp. + */ + private void displayValue(long bytes, int exp) { + int unit = 1024; + double value = (double) bytes / Math.pow(unit, exp); + modifyMarker = true; + valueText.setText(String.format(Locale.ENGLISH, "%.2f", value)); + } + + /** + * Unit we want to display for required amount of bytes. + * + * @param bytes + * Amount of bytes. + * @return {@link #BYTES}, {@link #KILO_BYTES} or {@link #MEGA_BYTES} + */ + private int getUnit(long bytes) { + bytes = Math.abs(bytes); + int unit = 1024; + if (bytes < unit) { + return 0; + } else { + int exp = (int) (Math.log(bytes) / Math.log(unit)); + if (exp < 4) { + return exp; + } else { + return 3; + } + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/LongPropertyControl.java b/inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/LongPropertyControl.java new file mode 100644 index 000000000..e49c7a337 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/LongPropertyControl.java @@ -0,0 +1,105 @@ +package info.novatec.inspectit.rcp.property.control.impl; + +import info.novatec.inspectit.cmr.property.configuration.impl.LongProperty; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.property.IPropertyUpdateListener; +import info.novatec.inspectit.rcp.property.control.AbstractPropertyControl; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.events.VerifyListener; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Text; + +/** + * {@link AbstractPropertyControl} for the long property. + * + * @author Ivan Senic + * + */ +public class LongPropertyControl extends AbstractPropertyControl { + + /** + * Text to display long value. + */ + private Text text; + + /** + * Default constructor. + * + * @param property + * Property. + * @param propertyUpdateListener + * Property update listener to report updates to. + */ + public LongPropertyControl(LongProperty property, IPropertyUpdateListener propertyUpdateListener) { + super(property, propertyUpdateListener); + } + + /** + * {@inheritDoc} + */ + @Override + public Control createControl(Composite parent) { + text = new Text(parent, SWT.BORDER | SWT.RIGHT); + text.setText(NumberFormatter.formatLong(property.getValue())); + text.addVerifyListener(new VerifyListener() { + @Override + public void verifyText(VerifyEvent e) { + String oldText = text.getText(); + String update = e.text; + String newText = oldText.substring(0, e.start) + update + oldText.substring(e.end, oldText.length()); + + // allow blank text + if (StringUtils.isNotBlank(newText)) { + // allow minus to be specified only + if (1 == newText.length() && '-' == newText.charAt(0)) { + return; + } + + // otherwise prove we have a valid long number + try { + Long.parseLong(newText); + } catch (NumberFormatException exception) { + e.doit = false; + return; + } + } + } + }); + text.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + String valueText = text.getText(); + if (!valueText.isEmpty() && (valueText.charAt(0) != '-' || valueText.length() > 1)) { + Long value = Long.parseLong(valueText); + sendPropertyUpdateEvent(value); + } + } + }); + text.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + String valueText = text.getText(); + if (valueText.isEmpty()) { + text.setText(NumberFormatter.formatLong(getLastCorrectValue().longValue())); + } + } + }); + return text; + } + + /** + * {@inheritDoc} + */ + @Override + protected void showDefaultValue() { + text.setText(NumberFormatter.formatLong(property.getDefaultValue().longValue())); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/PercentagePropertyControl.java b/inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/PercentagePropertyControl.java new file mode 100644 index 000000000..22aa300c1 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/PercentagePropertyControl.java @@ -0,0 +1,79 @@ +package info.novatec.inspectit.rcp.property.control.impl; + +import info.novatec.inspectit.cmr.property.configuration.impl.PercentageProperty; +import info.novatec.inspectit.rcp.property.IPropertyUpdateListener; +import info.novatec.inspectit.rcp.property.control.AbstractPropertyControl; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Spinner; + +/** + * {@link AbstractPropertyControl} for the percentage property. + * + * @author Ivan Senic + * + */ +public class PercentagePropertyControl extends AbstractPropertyControl { + + /** + * Spinner for displaying the value. + */ + private Spinner spinner; + + /** + * Default constructor. + * + * @param property + * Property. + * @param propertyUpdateListener + * Property update listener to report updates to. + */ + public PercentagePropertyControl(PercentageProperty property, IPropertyUpdateListener propertyUpdateListener) { + super(property, propertyUpdateListener); + } + + /** + * {@inheritDoc} + */ + @Override + public Control createControl(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + GridLayout gridLayout = new GridLayout(2, false); + gridLayout.marginHeight = 0; + gridLayout.marginWidth = 0; + composite.setLayout(gridLayout); + + spinner = new Spinner(composite, SWT.BORDER | SWT.RIGHT); + spinner.setValues((int) (property.getValue().floatValue() * 100), 0, 100, 0, 1, 5); + spinner.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + spinner.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + int selection = spinner.getSelection(); + Float value = Float.valueOf(selection / 100f); + sendPropertyUpdateEvent(value); + } + }); + + Label percentage = new Label(composite, SWT.NONE); + percentage.setText("%"); + percentage.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); + + return composite; + } + + /** + * {@inheritDoc} + */ + @Override + protected void showDefaultValue() { + spinner.setSelection((int) (property.getDefaultValue().floatValue() * 100)); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/StringPropertyControl.java b/inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/StringPropertyControl.java new file mode 100644 index 000000000..5ef95d467 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/property/control/impl/StringPropertyControl.java @@ -0,0 +1,77 @@ +package info.novatec.inspectit.rcp.property.control.impl; + +import info.novatec.inspectit.cmr.property.configuration.impl.StringProperty; +import info.novatec.inspectit.rcp.property.IPropertyUpdateListener; +import info.novatec.inspectit.rcp.property.control.AbstractPropertyControl; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Text; + +/** + * {@link AbstractPropertyControl} for the string property. + * + * @author Ivan Senic + * + */ +public class StringPropertyControl extends AbstractPropertyControl { + + /** + * Text to display string value. + */ + private Text text; + + /** + * Default constructor. + * + * @param property + * Property. + * @param propertyUpdateListener + * Property update listener to report updates to. + */ + public StringPropertyControl(StringProperty property, IPropertyUpdateListener propertyUpdateListener) { + super(property, propertyUpdateListener); + } + + /** + * {@inheritDoc} + */ + @Override + public Control createControl(Composite parent) { + text = new Text(parent, SWT.BORDER); + text.setText(property.getValue()); + text.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + String value = text.getText(); + if (!value.isEmpty()) { + sendPropertyUpdateEvent(value); + } + } + }); + text.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + String value = text.getText(); + if (value.isEmpty()) { + text.setText(getLastCorrectValue()); + } + } + }); + return text; + } + + /** + * {@inheritDoc} + */ + @Override + protected void showDefaultValue() { + text.setText(property.getDefaultValue()); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/provider/ICmrRepositoryAndAgentProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/provider/ICmrRepositoryAndAgentProvider.java new file mode 100644 index 000000000..7945e0eff --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/provider/ICmrRepositoryAndAgentProvider.java @@ -0,0 +1,29 @@ +package info.novatec.inspectit.rcp.provider; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +/** + * Interface for UI elements that can provide {@link PlatformIdent} alongside CMR repository + * definition. + * + * @author Ivan Senic + * + */ +public interface ICmrRepositoryAndAgentProvider { + + /** + * Gives the {@link CmrRepositoryDefinition}. + * + * @return Gives the {@link CmrRepositoryDefinition}. + */ + CmrRepositoryDefinition getCmrRepositoryDefinition(); + + /** + * Returns the agent. + * + * @return {@link PlatformIdent}. + */ + PlatformIdent getPlatformIdent(); + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/provider/ICmrRepositoryProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/provider/ICmrRepositoryProvider.java new file mode 100644 index 000000000..ab61823e2 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/provider/ICmrRepositoryProvider.java @@ -0,0 +1,21 @@ +package info.novatec.inspectit.rcp.provider; + +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +/** + * INterface for all model classes that can provide a {@link CmrRepositoryDefinition} when they are + * selected. + * + * @author Ivan Senic + * + */ +public interface ICmrRepositoryProvider { + + /** + * Gives the {@link CmrRepositoryDefinition}. + * + * @return Gives the {@link CmrRepositoryDefinition}. + */ + CmrRepositoryDefinition getCmrRepositoryDefinition(); + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/provider/IInputDefinitionProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/provider/IInputDefinitionProvider.java new file mode 100644 index 000000000..2921211ca --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/provider/IInputDefinitionProvider.java @@ -0,0 +1,17 @@ +package info.novatec.inspectit.rcp.provider; + +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; + +/** + * Marker for all classes that can provide an {@link InputDefinition}. + * + * @author Ivan Senic + * + */ +public interface IInputDefinitionProvider { + + /** + * @return Returns input definition. + */ + InputDefinition getInputDefinition(); +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/provider/ILocalStorageDataProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/provider/ILocalStorageDataProvider.java new file mode 100644 index 000000000..f6bcba073 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/provider/ILocalStorageDataProvider.java @@ -0,0 +1,17 @@ +package info.novatec.inspectit.rcp.provider; + +import info.novatec.inspectit.storage.LocalStorageData; + +/** + * Interface for all model components that can provide the information about the local storage. + * + * @author Ivan Senic + * + */ +public interface ILocalStorageDataProvider { + + /** + * @return Returns the {@link LocalStorageData}. + */ + LocalStorageData getLocalStorageData(); +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/provider/IStorageDataProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/provider/IStorageDataProvider.java new file mode 100644 index 000000000..ddddc9d24 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/provider/IStorageDataProvider.java @@ -0,0 +1,27 @@ +package info.novatec.inspectit.rcp.provider; + +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.storage.StorageData; + +/** + * Interface for all model components that can provide the information about the storage. + * + * @author Ivan Senic + * + */ +public interface IStorageDataProvider { + + /** + * Gives the {@link CmrRepositoryDefinition} where Storage is located. + * + * @return Gives the {@link CmrRepositoryDefinition} or null if storage is local. + */ + CmrRepositoryDefinition getCmrRepositoryDefinition(); + + /** + * Returns the storage data. + * + * @return the storageData {@link StorageData}. + */ + StorageData getStorageData(); +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/CmrRepositoryChangeListener.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/CmrRepositoryChangeListener.java new file mode 100644 index 000000000..61aab49eb --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/CmrRepositoryChangeListener.java @@ -0,0 +1,60 @@ +package info.novatec.inspectit.rcp.repository; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; + +/** + * Extended {@link RepositoryChangeListener} only for events on the {@link CmrRepositoryDefinition} + * s. + * + * @author Ivan Senic + * + */ +public interface CmrRepositoryChangeListener { + + /** + * If the online status of the repository has been changed. + * + * @param repositoryDefinition + * {@link CmrRepositoryDefinition}. + * @param oldStatus + * Old status. + * @param newStatus + * New status. + */ + void repositoryOnlineStatusUpdated(CmrRepositoryDefinition repositoryDefinition, OnlineStatus oldStatus, OnlineStatus newStatus); + + /** + * If a repository has been added. + * + * @param cmrRepositoryDefinition + * the repository definition. + */ + void repositoryAdded(CmrRepositoryDefinition cmrRepositoryDefinition); + + /** + * If a repository has been removed. + * + * @param cmrRepositoryDefinition + * the repository definition. + */ + void repositoryRemoved(CmrRepositoryDefinition cmrRepositoryDefinition); + + /** + * Informs the listener that the repository data like name or description have been updated. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} that was updated. + */ + void repositoryDataUpdated(CmrRepositoryDefinition cmrRepositoryDefinition); + + /** + * Informs the listener that the provided agent on the repository has been deleted. + * + * @param cmrRepositoryDefinition + * the repository definition. + * @param agent + * Agent that was deleted. + */ + void repositoryAgentDeleted(CmrRepositoryDefinition cmrRepositoryDefinition, PlatformIdent agent); +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/CmrRepositoryDefinition.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/CmrRepositoryDefinition.java new file mode 100644 index 000000000..22e228947 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/CmrRepositoryDefinition.java @@ -0,0 +1,534 @@ +package info.novatec.inspectit.rcp.repository; + +import info.novatec.inspectit.cmr.service.ICmrManagementService; +import info.novatec.inspectit.cmr.service.IExceptionDataAccessService; +import info.novatec.inspectit.cmr.service.IGlobalDataAccessService; +import info.novatec.inspectit.cmr.service.IHttpTimerDataAccessService; +import info.novatec.inspectit.cmr.service.IInvocationDataAccessService; +import info.novatec.inspectit.cmr.service.IServerStatusService; +import info.novatec.inspectit.cmr.service.IServerStatusService.ServerStatus; +import info.novatec.inspectit.cmr.service.ISqlDataAccessService; +import info.novatec.inspectit.cmr.service.IStorageService; +import info.novatec.inspectit.cmr.service.ITimerDataAccessService; +import info.novatec.inspectit.cmr.service.cache.CachedDataService; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.provider.ICmrRepositoryProvider; +import info.novatec.inspectit.rcp.repository.service.RefreshEditorsCachedDataService; +import info.novatec.inspectit.rcp.repository.service.cmr.CmrServiceProvider; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * The CMR repository definition initializes the services exposed by the CMR. + * + * @author Patrice Bouillet + * @author Dirk Maucher + * @author Eduard Tudenhoefner + * @author Matthias Huber + * + */ +public class CmrRepositoryDefinition implements RepositoryDefinition, ICmrRepositoryProvider { + + /** + * Default CMR name. + */ + public static final String DEFAULT_NAME = "Local CMR"; + + /** + * Default CMR ip address. + */ + public static final String DEFAULT_IP = "localhost"; + + /** + * Default CMR port. + */ + public static final int DEFAULT_PORT = 8182; + + /** + * Default description. + */ + public static final String DEFAULT_DESCRIPTION = "This Central Management Repository (CMR) is automatically added by default when you first start the inspectIT."; + + /** + * Enumeration for the online status of {@link CmrRepositoryDefinition}. + * + * @author Ivan Senic + * + */ + public enum OnlineStatus { + + /** + * Unknown state before the first check. + */ + UNKNOWN, + + /** + * CMR is off-line. + */ + OFFLINE, + + /** + * Status is being checked. + */ + CHECKING, + + /** + * CMR is online. + */ + ONLINE; + + /** + * Defines if the status can be changed. + * + * @param newStatus + * New status + * @return True if the status change is allowed. + */ + public boolean canChangeTo(OnlineStatus newStatus) { + if (this.equals(newStatus)) { + return false; + } + if (newStatus.equals(UNKNOWN)) { + return false; + } + switch (this) { + case OFFLINE: + if (newStatus.equals(ONLINE)) { + return false; + } + case ONLINE: + if (newStatus.equals(OFFLINE)) { + return false; + } + default: + return true; + } + } + } + + /** + * The ip of the CMR. + */ + private final String ip; + + /** + * The port used by the CMR. + */ + private final int port; + + /** + * State of the CMR. + */ + private OnlineStatus onlineStatus; + + /** + * Key received from the serverStatusService for checking the validation of the registered IDs + * on the CMR. + */ + private String registrationIdKey; + + /** + * CMR name assigned by user. + */ + private String name; + + /** + * Optional description for the CMR. + */ + private String description; + + /** + * The cached data service. + */ + private final CachedDataService cachedDataService; + + /** + * The sql data access service. + */ + private final ISqlDataAccessService sqlDataAccessService; + + /** + * The invocation data access service. + */ + private final IInvocationDataAccessService invocationDataAccessService; + + /** + * The exception data access service. + */ + private final IExceptionDataAccessService exceptionDataAccessService; + + /** + * The server status service exposed by the CMR and initialized by Spring. + */ + private final IServerStatusService serverStatusService; + + /** + * The buffer data access service. + */ + private ICmrManagementService cmrManagementService; + + /** + * The timer data access service. + */ + private ITimerDataAccessService timerDataAccessService; + + /** + * The http timer data access service. + */ + private IHttpTimerDataAccessService httpTimerDataAccessService; + + /** + * The {@link IGlobalDataAccessService}. + */ + private IGlobalDataAccessService globalDataAccessService; + + /** + * The storage service. + */ + private IStorageService storageService; + + /** + * CMR repository change listeners. + */ + private List cmrRepositoryChangeListeners = new ArrayList(1); + + /** + * Calls default constructor with name 'Undefined'. + * + * @param ip + * The ip of the CMR. + * @param port + * The port used by the CMR. + */ + public CmrRepositoryDefinition(String ip, int port) { + this(ip, port, "Undefined"); + } + + /** + * The default constructor of this class. The ip and port is mandatory to create the connection. + * + * @param ip + * The ip of the CMR. + * @param port + * The port used by the CMR. + * @param name + * The name of the CMR assigned by user. + */ + public CmrRepositoryDefinition(String ip, int port, String name) { + this.ip = ip; + this.port = port; + this.onlineStatus = OnlineStatus.UNKNOWN; + this.name = name; + + CmrServiceProvider cmrServiceProvider = InspectIT.getService(CmrServiceProvider.class); + + sqlDataAccessService = cmrServiceProvider.getSqlDataAccessService(this); + serverStatusService = cmrServiceProvider.getServerStatusService(this); + invocationDataAccessService = cmrServiceProvider.getInvocationDataAccessService(this); + exceptionDataAccessService = cmrServiceProvider.getExceptionDataAccessService(this); + httpTimerDataAccessService = cmrServiceProvider.getHttpTimerDataAccessService(this); + cmrManagementService = cmrServiceProvider.getCmrManagementService(this); + timerDataAccessService = cmrServiceProvider.getTimerDataAccessService(this); + globalDataAccessService = cmrServiceProvider.getGlobalDataAccessService(this); + storageService = cmrServiceProvider.getStorageService(this); + + cachedDataService = new RefreshEditorsCachedDataService(globalDataAccessService, this); + } + + /** + * {@inheritDoc} + */ + @Override + public CachedDataService getCachedDataService() { + return cachedDataService; + } + + /** + * {@inheritDoc} + */ + @Override + public IExceptionDataAccessService getExceptionDataAccessService() { + return exceptionDataAccessService; + } + + /** + * {@inheritDoc} + */ + @Override + public ISqlDataAccessService getSqlDataAccessService() { + return sqlDataAccessService; + } + + /** + * {@inheritDoc} + */ + @Override + public IInvocationDataAccessService getInvocationDataAccessService() { + return invocationDataAccessService; + } + + /** + * {@inheritDoc} + */ + public ICmrManagementService getCmrManagementService() { + return cmrManagementService; + } + + /** + * {@inheritDoc} + */ + @Override + public ITimerDataAccessService getTimerDataAccessService() { + return timerDataAccessService; + } + + /** + * {@inheritDoc} + */ + public IStorageService getStorageService() { + return storageService; + } + + /** + * {@inheritDoc} + */ + @Override + public IHttpTimerDataAccessService getHttpTimerDataAccessService() { + return httpTimerDataAccessService; + } + + /** + * {@inheritDoc} + */ + @Override + public IGlobalDataAccessService getGlobalDataAccessService() { + return globalDataAccessService; + } + + /** + * {@inheritDoc} + */ + @Override + public String getIp() { + return ip; + } + + /** + * {@inheritDoc} + */ + @Override + public int getPort() { + return port; + } + + /** + * Registers a CMR repository change listener to this CMR if it was not already. + * + * @param cmrRepositoryChangeListener + * {@link CmrRepositoryChangeListener}. + */ + public void addCmrRepositoryChangeListener(CmrRepositoryChangeListener cmrRepositoryChangeListener) { + synchronized (cmrRepositoryChangeListeners) { + if (!cmrRepositoryChangeListeners.contains(cmrRepositoryChangeListener)) { + cmrRepositoryChangeListeners.add(cmrRepositoryChangeListener); + } + } + } + + /** + * Removes a CMR repository change listener to this CMR. + * + * @param cmrRepositoryChangeListener + * {@link CmrRepositoryChangeListener}. + */ + public void removeCmrRepositoryChangeListener(CmrRepositoryChangeListener cmrRepositoryChangeListener) { + synchronized (cmrRepositoryChangeListeners) { + cmrRepositoryChangeListeners.remove(cmrRepositoryChangeListener); + } + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name + * the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the description + */ + public String getDescription() { + return description; + } + + /** + * @param description + * the description to set + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * @return the onlineStatus + */ + public OnlineStatus getOnlineStatus() { + return onlineStatus; + } + + /** + * If the CMR is online invokes the {@link IServerStatusService} to get the version. Otherwise + * returns 'N/A'. + * + * @return Version of this CMR. + */ + public String getVersion() { + if (onlineStatus != OnlineStatus.OFFLINE) { + try { + return serverStatusService.getVersion(); + } catch (Exception e) { + return IServerStatusService.VERSION_NOT_AVAILABLE; + } + } else { + return IServerStatusService.VERSION_NOT_AVAILABLE; + } + } + + /** + * Updates the status of the CMR if possible. + * + * @param newStatus + * New status. + * @return True if change was successful, false if the change is not allowed. + */ + public boolean changeOnlineStatus(OnlineStatus newStatus) { + if (onlineStatus.canChangeTo(newStatus)) { + OnlineStatus oldStatus = onlineStatus; + onlineStatus = newStatus; + synchronized (cmrRepositoryChangeListeners) { + for (CmrRepositoryChangeListener changeListener : cmrRepositoryChangeListeners) { + changeListener.repositoryOnlineStatusUpdated(this, oldStatus, newStatus); + } + } + return true; + } + return false; + } + + /** + * Refreshes the online status. + */ + public void refreshOnlineStatus() { + this.changeOnlineStatus(OnlineStatus.CHECKING); + boolean isOnline = isOnline(); + if (isOnline) { + this.changeOnlineStatus(OnlineStatus.ONLINE); + } else { + this.changeOnlineStatus(OnlineStatus.OFFLINE); + } + } + + /** + * Returns if the server is online by checking the {@link IServerStatusService}. + * + * @return Returns if the server is online by checking the {@link IServerStatusService}. + */ + private boolean isOnline() { + try { + ServerStatus status = serverStatusService.getServerStatus(); + if (ServerStatus.SERVER_ONLINE == status) { + checkKey(status.getRegistrationIdsValidationKey()); + return true; + } + return false; + } catch (Exception e) { + return false; + } + } + + /** + * {@inheritDoc} + */ + public CmrRepositoryDefinition getCmrRepositoryDefinition() { + return this; + } + + /** + * Check if key has changed and fire the refresh idents if necessary. + * + * @param newKey + * New key received from status. + */ + private void checkKey(String newKey) { + boolean isRefreshIdents = false; + if (null == registrationIdKey) { + registrationIdKey = newKey; + } else { + isRefreshIdents = !Objects.equals(registrationIdKey, newKey); // NOPMD + registrationIdKey = newKey; + } + + if (isRefreshIdents) { + cachedDataService.triggerRefreshIdents(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((ip == null) ? 0 : ip.hashCode()); + result = prime * result + port; + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + CmrRepositoryDefinition other = (CmrRepositoryDefinition) obj; + if (ip == null) { + if (other.ip != null) { + return false; + } + } else if (!ip.equals(other.ip)) { + return false; + } + if (port != other.port) { + return false; + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return "Repository definition :: Name=" + name + " IP=" + ip + " Port=" + port; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/CmrRepositoryManager.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/CmrRepositoryManager.java new file mode 100644 index 000000000..bbfa8ac56 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/CmrRepositoryManager.java @@ -0,0 +1,304 @@ +package info.novatec.inspectit.rcp.repository; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.preferences.PreferencesUtils; +import info.novatec.inspectit.rcp.util.ListenerList; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.ui.progress.IProgressConstants; + +/** + * The repository manager only for {@link CmrRepositoryDefinition}s. + * + * @author Ivan Senic + * + */ +public class CmrRepositoryManager { + + /** + * /** Update online repository status job repetition time in milliseconds. + */ + public static final long UPDATE_JOB_REPETITION = 60000; + + /** + * The list containing the available {@link RepositoryDefinition} objects. + */ + private List cmrRepositoryDefinitions = new ArrayList(); + + /** + * The list of listeners to be notified. + */ + private ListenerList cmrRepositoryChangeListeners = new ListenerList(); + + /** + * Map of jobs. + */ + private Map repositoryUpdateJobMap = new ConcurrentHashMap(); + + /** + * Default constructor. + *

+ * Loads the repository definitions from the preference store. + */ + public CmrRepositoryManager() { + List savedCmrs = PreferencesUtils.getCmrRepositoryDefinitions(); + if (CollectionUtils.isNotEmpty(savedCmrs)) { + cmrRepositoryDefinitions.addAll(savedCmrs); + for (CmrRepositoryDefinition cmrRepositoryDefinition : cmrRepositoryDefinitions) { + for (CmrRepositoryChangeListener repositoryChangeListener : cmrRepositoryChangeListeners) { + cmrRepositoryDefinition.addCmrRepositoryChangeListener(repositoryChangeListener); + } + UpdateRepositoryJob updateRepositoryJob = new UpdateRepositoryJob(cmrRepositoryDefinition, true); + updateRepositoryJob.schedule(); + repositoryUpdateJobMap.put(cmrRepositoryDefinition, updateRepositoryJob); + } + } + } + + /** + * Adds a repository definition handled by this manager. + * + * @param cmrRepositoryDefinition + * The definition to add. + */ + public void addCmrRepositoryDefinition(CmrRepositoryDefinition cmrRepositoryDefinition) { + if (!cmrRepositoryDefinitions.contains(cmrRepositoryDefinition)) { + for (CmrRepositoryChangeListener repositoryChangeListener : cmrRepositoryChangeListeners) { + cmrRepositoryDefinition.addCmrRepositoryChangeListener(repositoryChangeListener); + } + cmrRepositoryDefinitions.add(cmrRepositoryDefinition); + + savePreference(); + + for (CmrRepositoryChangeListener repositoryChangeListener : cmrRepositoryChangeListeners) { + repositoryChangeListener.repositoryAdded(cmrRepositoryDefinition); + } + + UpdateRepositoryJob updateRepositoryJob = new UpdateRepositoryJob(cmrRepositoryDefinition, true); + updateRepositoryJob.schedule(); + repositoryUpdateJobMap.put(cmrRepositoryDefinition, updateRepositoryJob); + } + } + + /** + * Removes a repository definition and notifies all registered listeners. + * + * @param cmrRepositoryDefinition + * The definition to remove. + */ + public void removeCmrRepositoryDefinition(CmrRepositoryDefinition cmrRepositoryDefinition) { + for (CmrRepositoryChangeListener repositoryChangeListener : cmrRepositoryChangeListeners) { + cmrRepositoryDefinition.removeCmrRepositoryChangeListener(repositoryChangeListener); + } + cmrRepositoryDefinitions.remove(cmrRepositoryDefinition); + + savePreference(); + + for (CmrRepositoryChangeListener repositoryChangeListener : cmrRepositoryChangeListeners) { + repositoryChangeListener.repositoryRemoved(cmrRepositoryDefinition); + } + + UpdateRepositoryJob updateRepositoryJob = repositoryUpdateJobMap.remove(cmrRepositoryDefinition); + if (null != updateRepositoryJob) { + updateRepositoryJob.cancel(); + } + } + + /** + * Forces the CMR Online update check. If the {@link CmrRepositoryDefinition} to check is not on + * the current list of repositories, this method will create a small job to check online status, + * but this job won't be rescheduled. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + * @return Returns the job that will be performing the update. Caller can use this job to react + * on the job being done. + */ + public UpdateRepositoryJob forceCmrRepositoryOnlineStatusUpdate(final CmrRepositoryDefinition cmrRepositoryDefinition) { + UpdateRepositoryJob updateRepositoryJob = repositoryUpdateJobMap.get(cmrRepositoryDefinition); + if (null != updateRepositoryJob) { + if (updateRepositoryJob.cancel()) { + updateRepositoryJob.schedule(); + } + } + return updateRepositoryJob; + } + + /** + * Forces update of all repositories. + * + * @return Returns the collection of jobs that will be performing the update. Caller can use + * these jobs to react on the one or more jobs being done. + */ + public Collection forceAllCmrRepositoriesOnlineStatusUpdate() { + List jobs = new ArrayList(); + for (CmrRepositoryDefinition cmrRepositoryDefinition : cmrRepositoryDefinitions) { + jobs.add(this.forceCmrRepositoryOnlineStatusUpdate(cmrRepositoryDefinition)); + } + return jobs; + } + + /** + * Returns all registered repository definitions handled by this manager. The list is + * unmodifiable. + * + * @return The list of repository definitions. + */ + public List getCmrRepositoryDefinitions() { + return Collections.unmodifiableList(cmrRepositoryDefinitions); + } + + /** + * Adds a listener which notifies on certain events. + * + * @param repositoryChangeListener + * The listener to add. + */ + public void addCmrRepositoryChangeListener(CmrRepositoryChangeListener repositoryChangeListener) { + cmrRepositoryChangeListeners.add(repositoryChangeListener); + + for (CmrRepositoryDefinition cmrRepositoryDefinition : cmrRepositoryDefinitions) { + cmrRepositoryDefinition.addCmrRepositoryChangeListener(repositoryChangeListener); + } + } + + /** + * Removes the listener. + * + * @param repositoryChangeListener + * The listener to remove. + */ + public void removeCmrRepositoryChangeListener(CmrRepositoryChangeListener repositoryChangeListener) { + cmrRepositoryChangeListeners.remove(repositoryChangeListener); + + for (CmrRepositoryDefinition cmrRepositoryDefinition : cmrRepositoryDefinitions) { + cmrRepositoryDefinition.removeCmrRepositoryChangeListener(repositoryChangeListener); + } + } + + /** + * Cancels all the update repository jobs. The method will return only when all jobs are + * canceled. + */ + public void cancelAllUpdateRepositoriesJobs() { + for (UpdateRepositoryJob updateRepositoryJob : repositoryUpdateJobMap.values()) { + while (!updateRepositoryJob.cancel()) { + try { + updateRepositoryJob.join(); + } catch (InterruptedException e) { + break; + } + } + } + } + + /** + * Updates the {@link CmrRepositoryDefinition} entry in the preferences. + * + * @param cmrRepositoryDefinition + * Repository to update. + */ + public void updateCmrRepositoryDefinitionData(CmrRepositoryDefinition cmrRepositoryDefinition) { + this.savePreference(); + for (CmrRepositoryChangeListener listener : cmrRepositoryChangeListeners) { + listener.repositoryDataUpdated(cmrRepositoryDefinition); + } + } + + /** + * Informs all listener that the provided agent on the repository has been deleted. + * + * @param cmrRepositoryDefinition + * the repository definition. + * @param agent + * Agent that was deleted. + */ + public void repositoryAgentDeleted(CmrRepositoryDefinition cmrRepositoryDefinition, PlatformIdent agent) { + for (CmrRepositoryChangeListener listener : cmrRepositoryChangeListeners) { + listener.repositoryAgentDeleted(cmrRepositoryDefinition, agent); + } + } + + /** + * Save the preferences to the backend store. + */ + private void savePreference() { + List toSave = new ArrayList(); + for (CmrRepositoryDefinition repositoryDefinition : cmrRepositoryDefinitions) { + toSave.add(repositoryDefinition); + } + PreferencesUtils.saveCmrRepositoryDefinitions(toSave, false); + } + + /** + * Update online status of all repositories job. + * + * @author Ivan Senic + * + */ + public static class UpdateRepositoryJob extends Job { + + /** + * CMR to update. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * Should job be rescheduled after its execution. + */ + private boolean rescheduleJob; + + /** + * Default constructor. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to update. + * @param rescheduleJob + * If job should be rescheduled after execution. + */ + public UpdateRepositoryJob(CmrRepositoryDefinition cmrRepositoryDefinition, boolean rescheduleJob) { + super("Updating online status of CMR repository " + cmrRepositoryDefinition.getIp() + ":" + cmrRepositoryDefinition.getPort()); + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + this.rescheduleJob = rescheduleJob; + this.setUser(false); + this.setProperty(IProgressConstants.ICON_PROPERTY, InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_SERVER_REFRESH_SMALL)); + } + + /** + * {@inheritDoc} + */ + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + cmrRepositoryDefinition.refreshOnlineStatus(); + return Status.OK_STATUS; + } finally { + if (rescheduleJob) { + this.schedule(UPDATE_JOB_REPETITION); + } + } + } + + /** + * @return the cmrRepositoryDefinition + */ + public CmrRepositoryDefinition getCmrRepositoryDefinition() { + return cmrRepositoryDefinition; + } + + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/RepositoryDefinition.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/RepositoryDefinition.java new file mode 100644 index 000000000..92b6d1cde --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/RepositoryDefinition.java @@ -0,0 +1,88 @@ +package info.novatec.inspectit.rcp.repository; + +import info.novatec.inspectit.cmr.service.ICachedDataService; +import info.novatec.inspectit.cmr.service.IExceptionDataAccessService; +import info.novatec.inspectit.cmr.service.IGlobalDataAccessService; +import info.novatec.inspectit.cmr.service.IHttpTimerDataAccessService; +import info.novatec.inspectit.cmr.service.IInvocationDataAccessService; +import info.novatec.inspectit.cmr.service.ISqlDataAccessService; +import info.novatec.inspectit.cmr.service.ITimerDataAccessService; + +/** + * The interface to the repository definition. A repository can be anywhere and anything, the + * implementation will provide the details on how to access the information. + * + * @author Patrice Bouillet + */ +public interface RepositoryDefinition { + + /** + * Returns the IP of the definition. + * + * @return The IP. + */ + String getIp(); + + /** + * Returns the port of the definition. + * + * @return The port. + */ + int getPort(); + + /** + * Returns the repository symbolic name. + * + * @return Returns the repository symbolic name. + */ + String getName(); + + /** + * Returns the invocation data access service for this repository definition. + * + * @return The invocation data access service. + */ + IInvocationDataAccessService getInvocationDataAccessService(); + + /** + * Returns the sql data access service for this repository definition. + * + * @return The sql data access service. + */ + ISqlDataAccessService getSqlDataAccessService(); + + /** + * Returns the exception data access service for this repository definition. + * + * @return The exception data access service. + */ + IExceptionDataAccessService getExceptionDataAccessService(); + + /** + * Returns the global data access service for this repository definition. + * + * @return The global data access service. + */ + ICachedDataService getCachedDataService(); + + /** + * Returns the timer data access service for this repository definition. + * + * @return The timer data access service. + */ + ITimerDataAccessService getTimerDataAccessService(); + + /** + * Returns the http timer data access service for this repository definition. + * + * @return The http timer data access service. + */ + IHttpTimerDataAccessService getHttpTimerDataAccessService(); + + /** + * Returns the {@link IGlobalDataAccessService}. + * + * @return Returns the {@link IGlobalDataAccessService}. + */ + IGlobalDataAccessService getGlobalDataAccessService(); +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/StorageRepositoryDefinition.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/StorageRepositoryDefinition.java new file mode 100644 index 000000000..520d2fee1 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/StorageRepositoryDefinition.java @@ -0,0 +1,264 @@ +package info.novatec.inspectit.rcp.repository; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.cmr.service.IExceptionDataAccessService; +import info.novatec.inspectit.cmr.service.IGlobalDataAccessService; +import info.novatec.inspectit.cmr.service.IHttpTimerDataAccessService; +import info.novatec.inspectit.cmr.service.IInvocationDataAccessService; +import info.novatec.inspectit.cmr.service.ISqlDataAccessService; +import info.novatec.inspectit.cmr.service.ITimerDataAccessService; +import info.novatec.inspectit.cmr.service.cache.CachedDataService; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.indexing.storage.IStorageTreeComponent; +import info.novatec.inspectit.rcp.repository.service.storage.StorageServiceProvider; +import info.novatec.inspectit.storage.LocalStorageData; + +import java.util.List; + +import com.google.common.base.Objects; + +/** + * Storage repository definition. This {@link RepositoryDefinition} has a direct usage of a + * {@link CmrRepositoryDefinition} where storage is located. + * + * @author Ivan Senic + * + */ +public class StorageRepositoryDefinition implements RepositoryDefinition { + + /** + * {@link CmrRepositoryDefinition}. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * {@link LocalStorageData}. + */ + private LocalStorageData localStorageData; + + /** + * {@link IInvocationDataAccessService} service. + */ + private IInvocationDataAccessService invocationDataAccessService; + + /** + * {@link IGlobalDataAccessService} service. + */ + private IGlobalDataAccessService globalDataAccessService; + + /** + * Caching component. + */ + private CachedDataService cachedDataService; + + /** + * {@link IExceptionDataAccessService}. + */ + private IExceptionDataAccessService exceptionDataAccessService; + + /** + * {@link ISqlDataAccessService}. + */ + private ISqlDataAccessService sqlDataAccessService; + + /** + * {@link ITimerDataAccessService}. + */ + private ITimerDataAccessService timerDataAccessService; + + /** + * {@link IHttpTimerDataAccessService}. + */ + private IHttpTimerDataAccessService httpTimerDataAccessService; + + /** + * {@link StorageServiceProvider} for instantiating storage services. + */ + private StorageServiceProvider storageServiceProvider; + + /** + * Indexing tree for storage. + */ + private IStorageTreeComponent indexingTree; + + /** + * Involved agents. + */ + private List agents; + + /** + * {@inheritDoc} + */ + public String getIp() { + return cmrRepositoryDefinition.getIp(); + } + + /** + * {@inheritDoc} + */ + public int getPort() { + return cmrRepositoryDefinition.getPort(); + } + + /** + * {@inheritDoc} + */ + public String getName() { + return localStorageData.getName(); + } + + /** + * {@inheritDoc} + */ + public IInvocationDataAccessService getInvocationDataAccessService() { + return invocationDataAccessService; + } + + /** + * {@inheritDoc} + */ + public ISqlDataAccessService getSqlDataAccessService() { + return sqlDataAccessService; + } + + /** + * {@inheritDoc} + */ + public IExceptionDataAccessService getExceptionDataAccessService() { + return exceptionDataAccessService; + } + + /** + * {@inheritDoc} + */ + @Override + public IGlobalDataAccessService getGlobalDataAccessService() { + return globalDataAccessService; + } + + /** + * {@inheritDoc} + */ + @Override + public CachedDataService getCachedDataService() { + return cachedDataService; + } + + /** + * {@inheritDoc} + */ + public ITimerDataAccessService getTimerDataAccessService() { + return timerDataAccessService; + } + + /** + * {@inheritDoc} + */ + public IHttpTimerDataAccessService getHttpTimerDataAccessService() { + return httpTimerDataAccessService; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public void initServices() { + // init services + globalDataAccessService = storageServiceProvider.createStorageGlobalDataAccessService(this, localStorageData, (IStorageTreeComponent) indexingTree, agents); + exceptionDataAccessService = storageServiceProvider.createStorageExceptionDataAccessService(this, localStorageData, (IStorageTreeComponent) indexingTree); + invocationDataAccessService = storageServiceProvider.createStorageInvocationDataAccessService(this, localStorageData, (IStorageTreeComponent) indexingTree); + sqlDataAccessService = storageServiceProvider.createStorageSqlDataAccessService(this, localStorageData, (IStorageTreeComponent) indexingTree); + timerDataAccessService = storageServiceProvider.createStorageTimerDataAccessService(this, localStorageData, (IStorageTreeComponent) indexingTree); + httpTimerDataAccessService = storageServiceProvider.createStorageHttpTimerDataAccessService(this, localStorageData, (IStorageTreeComponent) indexingTree); + + // for storage we use the regular cached data service because ids can never change + cachedDataService = new CachedDataService(globalDataAccessService); + } + + /** + * @return the cmrRepositoryDefinition + */ + public CmrRepositoryDefinition getCmrRepositoryDefinition() { + return cmrRepositoryDefinition; + } + + /** + * @param cmrRepositoryDefinition + * the cmrRepositoryDefinition to set + */ + public void setCmrRepositoryDefinition(CmrRepositoryDefinition cmrRepositoryDefinition) { + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + } + + /** + * @return the storageData + */ + public LocalStorageData getLocalStorageData() { + return localStorageData; + } + + /** + * @param localStorageData + * the storageData to set + */ + public void setLocalStorageData(LocalStorageData localStorageData) { + this.localStorageData = localStorageData; + } + + /** + * @param storageServiceProvider + * the storageServiceProvider to set + */ + public void setStorageServiceProvider(StorageServiceProvider storageServiceProvider) { + this.storageServiceProvider = storageServiceProvider; + } + + /** + * @param indexingTree + * the indexingTree to set + */ + public void setIndexingTree(IStorageTreeComponent indexingTree) { + this.indexingTree = indexingTree; + } + + /** + * @param agents + * the agents to set + */ + public void setAgents(List agents) { + this.agents = agents; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(localStorageData); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + StorageRepositoryDefinition that = (StorageRepositoryDefinition) object; + return Objects.equal(this.localStorageData, that.localStorageData); + + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/StorageRepositoryDefinitionProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/StorageRepositoryDefinitionProvider.java new file mode 100644 index 000000000..10e126423 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/StorageRepositoryDefinitionProvider.java @@ -0,0 +1,18 @@ +package info.novatec.inspectit.rcp.repository; + +/** + * Spring enhanced class for getting the {@link StorageRepositoryDefinition}. + * + * @author Ivan Senic + * + */ +public abstract class StorageRepositoryDefinitionProvider { + + /** + * Returns Spring instantiated {@link StorageRepositoryDefinition}. + * + * @return Spring instantiated {@link StorageRepositoryDefinition}. + */ + public abstract StorageRepositoryDefinition createStorageRepositoryDefinition(); + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/service/RefreshEditorsCachedDataService.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/RefreshEditorsCachedDataService.java new file mode 100644 index 000000000..439755634 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/RefreshEditorsCachedDataService.java @@ -0,0 +1,63 @@ +package info.novatec.inspectit.rcp.repository.service; + +import info.novatec.inspectit.cmr.service.IGlobalDataAccessService; +import info.novatec.inspectit.cmr.service.cache.CachedDataService; +import info.novatec.inspectit.rcp.editor.root.IRootEditor; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.Objects; + +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IEditorReference; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; + +/** + * The {@link CachedDataService} to be used on the UI. When refresh of idents is triggered, all + * editors on the given {@link CmrRepositoryDefinition} are refreshed so that correct data is + * displayed. + * + * @author Ivan Senic + * + */ +public class RefreshEditorsCachedDataService extends CachedDataService { + + /** + * Repository definition. + */ + private CmrRepositoryDefinition repositoryDefinition; + + /** + * @param globalDataAccessService + * {@link IGlobalDataAccessService} + * @param repositoryDefinition + * {@link RepositoryDefinition} + */ + public RefreshEditorsCachedDataService(IGlobalDataAccessService globalDataAccessService, CmrRepositoryDefinition repositoryDefinition) { + super(globalDataAccessService); + this.repositoryDefinition = repositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + protected void postRefreshIdents() { + // execute refresh of all opened editors since data is not valid any more + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + IWorkbenchWindow workbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + IEditorReference[] editors = workbenchWindow.getActivePage().getEditorReferences(); + for (IEditorReference editor : editors) { + IRootEditor rootEditor = (IRootEditor) editor.getEditor(false); + if (Objects.equals(rootEditor.getInputDefinition().getRepositoryDefinition(), repositoryDefinition)) { + rootEditor.doRefresh(); + } + } + } + }); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/CmrService.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/CmrService.java new file mode 100644 index 000000000..812bf2d34 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/CmrService.java @@ -0,0 +1,129 @@ +package info.novatec.inspectit.rcp.repository.service.cmr; + +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.storage.serializer.provider.SerializationManagerProvider; + +import org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean; + +/** + * Abstract class for all {@link CmrRepositoryDefinition} service classes. + * + * @author Ivan Senic + * + */ +public class CmrService implements ICmrService { + + /** + * Protocol used. + */ + private static final String PROTOCOL = "http://"; + + /** + * Remoting path. + */ + private static final String REMOTING = "/remoting/"; + + /** + * {@link CmrRepositoryDefinition}. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * Real service where calls will be executed. + */ + private Object service; + + /** + * Service interface. + */ + private Class serviceInterface; + + /** + * Service name. + */ + private String serviceName; + + /** + * The serialization manager for kryo. + */ + private SerializationManagerProvider serializationManagerProvider; + + /** + * {@inheritDoc} + */ + public void initService(CmrRepositoryDefinition cmrRepositoryDefinition) { + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + HttpInvokerProxyFactoryBean httpInvokerProxyFactoryBean = new HttpInvokerProxyFactoryBean(); + + // we need to set the class loader on our own + // the problems is that the service interface class can not be found + // I am not quite sure why, but this is suggested on several places as a patch + httpInvokerProxyFactoryBean.setBeanClassLoader(getClass().getClassLoader()); + + // using kryo (de-)serialization for the requests and responses + KryoSimpleHttpInvokerRequestExecutor kryoSimpleHttpInvokerRequestExecutor = new KryoSimpleHttpInvokerRequestExecutor(); + kryoSimpleHttpInvokerRequestExecutor.setBeanClassLoader(getClass().getClassLoader()); + kryoSimpleHttpInvokerRequestExecutor.setSerializationManagerProvider(serializationManagerProvider); + httpInvokerProxyFactoryBean.setHttpInvokerRequestExecutor(kryoSimpleHttpInvokerRequestExecutor); + + httpInvokerProxyFactoryBean.setServiceInterface(serviceInterface); + httpInvokerProxyFactoryBean.setServiceUrl(PROTOCOL + cmrRepositoryDefinition.getIp() + ":" + cmrRepositoryDefinition.getPort() + REMOTING + serviceName); + httpInvokerProxyFactoryBean.afterPropertiesSet(); + + service = httpInvokerProxyFactoryBean.getObject(); + } + + /** + * {@inheritDoc} + */ + public CmrRepositoryDefinition getCmrRepositoryDefinition() { + return cmrRepositoryDefinition; + } + + /** + * {@inheritDoc} + */ + public Object getService() { + return service; + } + + /** + * Sets {@link #serviceInterface}. + * + * @param serviceInterface + * New value for {@link #serviceInterface} + */ + public void setServiceInterface(Class serviceInterface) { + this.serviceInterface = serviceInterface; + } + + /** + * Gets {@link #serviceInterface}. + * + * @return {@link #serviceInterface} + */ + public Class getServiceInterface() { + return serviceInterface; + } + + /** + * Sets {@link #serviceName}. + * + * @param serviceName + * New value for {@link #serviceName} + */ + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + /** + * Sets {@link #serializationManagerProvider}. + * + * @param serializationManagerProvider + * New value for {@link #serializationManagerProvider} + */ + public void setSerializationManagerProvider(SerializationManagerProvider serializationManagerProvider) { + this.serializationManagerProvider = serializationManagerProvider; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/CmrServiceProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/CmrServiceProvider.java new file mode 100644 index 000000000..0f17050b8 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/CmrServiceProvider.java @@ -0,0 +1,202 @@ +package info.novatec.inspectit.rcp.repository.service.cmr; + +import info.novatec.inspectit.cmr.service.ICmrManagementService; +import info.novatec.inspectit.cmr.service.IExceptionDataAccessService; +import info.novatec.inspectit.cmr.service.IGlobalDataAccessService; +import info.novatec.inspectit.cmr.service.IHttpTimerDataAccessService; +import info.novatec.inspectit.cmr.service.IInvocationDataAccessService; +import info.novatec.inspectit.cmr.service.IServerStatusService; +import info.novatec.inspectit.cmr.service.ISqlDataAccessService; +import info.novatec.inspectit.cmr.service.IStorageService; +import info.novatec.inspectit.cmr.service.ITimerDataAccessService; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +/** + * Provider of the {@link ICmrService}s via Spring. + * + * @author Ivan Senic + * + */ +public abstract class CmrServiceProvider { + + /** + * Returns properly initialized {@link BufferService}. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to bound service to. + * @return Returns {@link BufferService}. + */ + public ICmrManagementService getCmrManagementService(CmrRepositoryDefinition cmrRepositoryDefinition) { + ICmrManagementService cmrManagementService = getCmrManagementService(); + ((ICmrService) cmrManagementService).initService(cmrRepositoryDefinition); + return cmrManagementService; + } + + /** + * Returns Spring created {@link BufferService}. + * + * @return Returns Spring created {@link BufferService}. + */ + protected abstract ICmrManagementService getCmrManagementService(); + + /** + * Returns properly initialized {@link ExceptionDataAccessService}. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to bound service to. + * @return Returns {@link ExceptionDataAccessService}. + */ + public IExceptionDataAccessService getExceptionDataAccessService(CmrRepositoryDefinition cmrRepositoryDefinition) { + IExceptionDataAccessService exceptionDataAccessService = getExceptionDataAccessService(); + ((ICmrService) exceptionDataAccessService).initService(cmrRepositoryDefinition); + return exceptionDataAccessService; + } + + /** + * Returns Spring created {@link ExceptionDataAccessService}. + * + * @return Returns Spring created {@link ExceptionDataAccessService}. + */ + protected abstract IExceptionDataAccessService getExceptionDataAccessService(); + + /** + * Returns properly initialized {@link GlobalDataAccessService}. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to bound service to. + * @return Returns {@link GlobalDataAccessService}. + */ + public IGlobalDataAccessService getGlobalDataAccessService(CmrRepositoryDefinition cmrRepositoryDefinition) { + IGlobalDataAccessService globalDataAccessService = getGlobalDataAccessService(); + ((ICmrService) globalDataAccessService).initService(cmrRepositoryDefinition); + return globalDataAccessService; + } + + /** + * Returns Spring created {@link GlobalDataAccessService}. + * + * @return Returns Spring created {@link GlobalDataAccessService}. + */ + protected abstract IGlobalDataAccessService getGlobalDataAccessService(); + + /** + * Returns properly initialized {@link InvocationDataAccessService}. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to bound service to. + * @return Returns {@link InvocationDataAccessService}. + */ + public IInvocationDataAccessService getInvocationDataAccessService(CmrRepositoryDefinition cmrRepositoryDefinition) { + IInvocationDataAccessService invocationDataAccessService = getInvocationDataAccessService(); + ((ICmrService) invocationDataAccessService).initService(cmrRepositoryDefinition); + return invocationDataAccessService; + } + + /** + * Returns Spring created {@link InvocationDataAccessService}. + * + * @return Returns Spring created {@link InvocationDataAccessService}. + */ + protected abstract IInvocationDataAccessService getInvocationDataAccessService(); + + /** + * Returns properly initialized {@link ServerStatusService}. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to bound service to. + * @return Returns {@link ServerStatusService}. + */ + public IServerStatusService getServerStatusService(CmrRepositoryDefinition cmrRepositoryDefinition) { + IServerStatusService serverStatusService = getServerStatusService(); + ((ICmrService) serverStatusService).initService(cmrRepositoryDefinition); + return serverStatusService; + } + + /** + * Returns Spring created {@link ServerStatusService}. + * + * @return Returns Spring created {@link ServerStatusService}. + */ + protected abstract IServerStatusService getServerStatusService(); + + /** + * Returns properly initialized {@link SqlDataAccessService}. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to bound service to. + * @return Returns {@link SqlDataAccessService}. + */ + public ISqlDataAccessService getSqlDataAccessService(CmrRepositoryDefinition cmrRepositoryDefinition) { + ISqlDataAccessService sqlDataAccessService = getSqlDataAccessService(); + ((ICmrService) sqlDataAccessService).initService(cmrRepositoryDefinition); + return sqlDataAccessService; + } + + /** + * Returns Spring created {@link SqlDataAccessService}. + * + * @return Returns Spring created {@link SqlDataAccessService}. + */ + protected abstract ISqlDataAccessService getSqlDataAccessService(); + + /** + * Returns properly initialized {@link TimerDataAccessService}. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to bound service to. + * @return Returns {@link TimerDataAccessService}. + */ + public ITimerDataAccessService getTimerDataAccessService(CmrRepositoryDefinition cmrRepositoryDefinition) { + ITimerDataAccessService timerDataAccessService = getTimerDataAccessService(); + ((ICmrService) timerDataAccessService).initService(cmrRepositoryDefinition); + return timerDataAccessService; + } + + /** + * Returns Spring created {@link TimerDataAccessService}. + * + * @return Returns Spring created {@link TimerDataAccessService}. + */ + protected abstract ITimerDataAccessService getTimerDataAccessService(); + + /** + * Returns properly initialized {@link TimerDataAccessService}. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to bound service to. + * @return Returns {@link TimerDataAccessService}. + */ + public IHttpTimerDataAccessService getHttpTimerDataAccessService(CmrRepositoryDefinition cmrRepositoryDefinition) { + IHttpTimerDataAccessService httpTimerDataAccessService = getHttpTimerDataAccessService(); + ((ICmrService) httpTimerDataAccessService).initService(cmrRepositoryDefinition); + return httpTimerDataAccessService; + } + + /** + * Returns Spring created {@link TimerDataAccessService}. + * + * @return Returns Spring created {@link TimerDataAccessService}. + */ + protected abstract IHttpTimerDataAccessService getHttpTimerDataAccessService(); + + /** + * Returns properly initialized {@link StorageService}. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to bound service to. + * @return Returns {@link StorageService}. + */ + public IStorageService getStorageService(CmrRepositoryDefinition cmrRepositoryDefinition) { + IStorageService storageService = getStorageService(); + ((ICmrService) storageService).initService(cmrRepositoryDefinition); + return storageService; + } + + /** + * Returns Spring created {@link StorageService}. + * + * @return Returns Spring created {@link StorageService}. + */ + protected abstract IStorageService getStorageService(); + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/ICmrService.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/ICmrService.java new file mode 100644 index 000000000..94e8e2a21 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/ICmrService.java @@ -0,0 +1,34 @@ +package info.novatec.inspectit.rcp.repository.service.cmr; + +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +/** + * Interface for all CMR services. + * + * @author Ivan Senic + * + */ +public interface ICmrService { + + /** + * Returns {@link CmrRepositoryDefinition} that service is bounded to. + * + * @return Returns {@link CmrRepositoryDefinition} that service is bounded to. + */ + CmrRepositoryDefinition getCmrRepositoryDefinition(); + + /** + * Initializes the service. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + */ + void initService(CmrRepositoryDefinition cmrRepositoryDefinition); + + /** + * Returns the service object. + * + * @return Returns the service object. + */ + Object getService(); +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/KryoSimpleHttpInvokerRequestExecutor.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/KryoSimpleHttpInvokerRequestExecutor.java new file mode 100644 index 000000000..433092e7b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/KryoSimpleHttpInvokerRequestExecutor.java @@ -0,0 +1,79 @@ +package info.novatec.inspectit.rcp.repository.service.cmr; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.storage.serializer.ISerializer; +import info.novatec.inspectit.storage.serializer.SerializationException; +import info.novatec.inspectit.storage.serializer.provider.SerializationManagerProvider; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.springframework.remoting.httpinvoker.SimpleHttpInvokerRequestExecutor; +import org.springframework.remoting.support.RemoteInvocation; +import org.springframework.remoting.support.RemoteInvocationResult; + +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +/** + * This class extends the simple http one by using Kryo for (de-)serializing. + * + * @author Patrice Bouillet + * + */ +public class KryoSimpleHttpInvokerRequestExecutor extends SimpleHttpInvokerRequestExecutor { + + /** + * The serialization manager provider. + */ + private SerializationManagerProvider serializationManagerProvider; + + /** + * {@inheritDoc} + */ + @Override + protected void writeRemoteInvocation(RemoteInvocation invocation, OutputStream os) throws IOException { + try { + ISerializer serializer = serializationManagerProvider.createSerializer(); + serializer.serialize(invocation, new Output(os)); + } catch (SerializationException e) { + InspectIT.getDefault().createErrorDialog(e.getMessage(), e, -1); + throw new IOException(e); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected RemoteInvocationResult readRemoteInvocationResult(InputStream is, String codebaseUrl) throws IOException, ClassNotFoundException { + try { + ISerializer serializer = serializationManagerProvider.createSerializer(); + return (RemoteInvocationResult) serializer.deserialize(new Input(is)); + } catch (SerializationException e) { + InspectIT.getDefault().createErrorDialog(e.getMessage(), e, -1); + throw new IOException(e); + } + } + + /** + * Gets {@link #serializationManagerProvider}. + * + * @return {@link #serializationManagerProvider} + */ + public SerializationManagerProvider getSerializationManagerProvider() { + return serializationManagerProvider; + } + + /** + * Sets {@link #serializationManagerProvider}. + * + * @param serializationManagerProvider + * New value for {@link #serializationManagerProvider} + */ + public void setSerializationManagerProvider(SerializationManagerProvider serializationManagerProvider) { + this.serializationManagerProvider = serializationManagerProvider; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/proxy/CachingPlatformIdentInterceptor.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/proxy/CachingPlatformIdentInterceptor.java new file mode 100644 index 000000000..8d2e55a3b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/proxy/CachingPlatformIdentInterceptor.java @@ -0,0 +1,34 @@ +package info.novatec.inspectit.rcp.repository.service.cmr.proxy; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +/** + * An interceptor that populates the CachedDataService cache with the loaded {@link PlatformIdent} + * from the service. + * + * @author Ivan Senic + * + */ +public class CachingPlatformIdentInterceptor implements MethodInterceptor { + + /** + * {@inheritDoc} + */ + @Override + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + Object result = methodInvocation.proceed(); + if (result instanceof PlatformIdent && InterceptorUtils.isServiceMethod(methodInvocation)) { + CmrRepositoryDefinition cmrRepositoryDefinition = InterceptorUtils.getRepositoryDefinition(methodInvocation); + if (null != cmrRepositoryDefinition) { + PlatformIdent platformIdent = (PlatformIdent) result; + cmrRepositoryDefinition.getCachedDataService().refreshData(platformIdent); + } + } + return result; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/proxy/InterceptorUtils.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/proxy/InterceptorUtils.java new file mode 100644 index 000000000..3b782f511 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/proxy/InterceptorUtils.java @@ -0,0 +1,91 @@ +package info.novatec.inspectit.rcp.repository.service.cmr.proxy; + +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.service.cmr.ICmrService; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.framework.ReflectiveMethodInvocation; + +import com.google.common.base.Defaults; + +/** + * Utilities that will be used in interceptors. + * + * @author Ivan Senic + * + */ +public final class InterceptorUtils { + + /** + * Private constructor. + */ + private InterceptorUtils() { + } + + /** + * Is service method. + * + * @param methodInvocation + * Method invocation. + * @return Return if it is service method. + */ + public static boolean isServiceMethod(MethodInvocation methodInvocation) { + return !methodInvocation.getMethod().getDeclaringClass().equals(ICmrService.class); + } + + /** + * Tries to get the {@link CmrRepositoryDefinition} from the proxied {@link ICmrService} object. + * + * @param paramMethodInvocation + * {@link MethodInvocation}. + * @return CMR invoked or null. + */ + public static CmrRepositoryDefinition getRepositoryDefinition(MethodInvocation paramMethodInvocation) { + if (paramMethodInvocation instanceof ReflectiveMethodInvocation) { + ReflectiveMethodInvocation reflectiveMethodInvocation = (ReflectiveMethodInvocation) paramMethodInvocation; + Object service = reflectiveMethodInvocation.getThis(); + if (service instanceof ICmrService) { + ICmrService cmrService = (ICmrService) service; + CmrRepositoryDefinition cmrRepositoryDefinition = cmrService.getCmrRepositoryDefinition(); + return cmrRepositoryDefinition; + } + } + return null; + } + + /** + * Checks if the return type of the {@link java.lang.reflect.Method} invoked by + * {@link MethodInvocation} is one of tree major collection types (List, Map, Set) and if it is + * returns the empty collection of correct type. Otherwise it returns null. + * + * @param paramMethodInvocation + * {@link MethodInvocation} + * @return If the method invoked by {@link MethodInvocation} is one of tree major collection + * types (List, Map, Set) method returns the empty collection of correct type. Otherwise + * it returns null. + */ + public static Object getDefaultReturnValue(MethodInvocation paramMethodInvocation) { + Class returnType = paramMethodInvocation.getMethod().getReturnType(); + if (returnType.isAssignableFrom(List.class)) { + return Collections.emptyList(); + } else if (returnType.isAssignableFrom(Map.class)) { + return Collections.emptyMap(); + } else if (returnType.isAssignableFrom(Set.class)) { + return Collections.emptySet(); + } else if (returnType.isPrimitive()) { + try { + return Defaults.defaultValue(returnType); + } catch (Exception e) { + return null; + } + } else { + return null; + } + + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/proxy/ServiceInterfaceDelegateInterceptor.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/proxy/ServiceInterfaceDelegateInterceptor.java new file mode 100644 index 000000000..6903f19f0 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/proxy/ServiceInterfaceDelegateInterceptor.java @@ -0,0 +1,61 @@ +package info.novatec.inspectit.rcp.repository.service.cmr.proxy; + +import info.novatec.inspectit.rcp.repository.service.cmr.ICmrService; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +/** + * {@link MethodInterceptor} that delegates the call to the concrete service of a + * {@link ICmrService} class. + * + * @author Ivan Senic + * + */ +public class ServiceInterfaceDelegateInterceptor implements MethodInterceptor { + + /** + * {@inheritDoc} + */ + @Override + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + Object thisObject = methodInvocation.getThis(); + if (thisObject instanceof ICmrService) { + ICmrService cmrService = (ICmrService) thisObject; + if (InterceptorUtils.isServiceMethod(methodInvocation)) { + Object concreteService = cmrService.getService(); + Object returnVal = invokeUsingReflection(concreteService, methodInvocation.getMethod(), methodInvocation.getArguments()); + return returnVal; + } else { + return methodInvocation.proceed(); + } + } else { + throw new Exception("ServiceInterfaceIntroductionInterceptor not bounded to the ICmrService class."); + } + } + + /** + * Invokes the concrete object using reflection. + * + * @param concreteService + * Service to invoke. + * @param method + * Method to invoke. + * @param arguments + * Arguments. + * @throws Throwable + * If any other exception occurs. + * @return Return value. + */ + private Object invokeUsingReflection(Object concreteService, Method method, Object[] arguments) throws Throwable { + try { + return method.invoke(concreteService, arguments); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/proxy/ServiceMethodInterceptor.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/proxy/ServiceMethodInterceptor.java new file mode 100644 index 000000000..fbce39a54 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/cmr/proxy/ServiceMethodInterceptor.java @@ -0,0 +1,76 @@ +package info.novatec.inspectit.rcp.repository.service.cmr.proxy; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.storage.serializer.SerializationException; + +import java.net.ConnectException; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.remoting.RemoteConnectFailureException; + +/** + * Our service method interceptor that will catch {@link InspectITCommunicationException} and if the + * problem was {@link RemoteConnectFailureException}, it will update the online status of the CMR. + * This interceptor will also show a error message. + * + * @author Ivan Senic + * + */ +public class ServiceMethodInterceptor implements MethodInterceptor { + + /** + * {@inheritDoc} + */ + public Object invoke(MethodInvocation paramMethodInvocation) throws Throwable { + try { + Object rval = paramMethodInvocation.proceed(); + CmrRepositoryDefinition cmrRepositoryDefinition = InterceptorUtils.getRepositoryDefinition(paramMethodInvocation); + if (null != cmrRepositoryDefinition && InterceptorUtils.isServiceMethod(paramMethodInvocation)) { + if (cmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.OFFLINE) { + InspectIT.getDefault().getCmrRepositoryManager().forceCmrRepositoryOnlineStatusUpdate(cmrRepositoryDefinition); + } + } else if (null == cmrRepositoryDefinition) { + throw new RuntimeException("Service proxy not bounded to the CMR repository definition"); + } + return rval; + } catch (RemoteConnectFailureException e) { + handleConnectionFailure(paramMethodInvocation, e); + return InterceptorUtils.getDefaultReturnValue(paramMethodInvocation); + } catch (ConnectException e) { + handleConnectionFailure(paramMethodInvocation, e); + return InterceptorUtils.getDefaultReturnValue(paramMethodInvocation); + } catch (SerializationException e) { + CmrRepositoryDefinition cmrRepositoryDefinition = InterceptorUtils.getRepositoryDefinition(paramMethodInvocation); + InspectIT.getDefault().createErrorDialog( + "CMR repository version (" + cmrRepositoryDefinition.getVersion() + ") is not compatible with the version of the inspectIT UI. Communication between two is failing.", e, -1); + return InterceptorUtils.getDefaultReturnValue(paramMethodInvocation); + } catch (Exception e) { + InspectIT.getDefault().createErrorDialog(e.getMessage(), e.getCause() != null ? e.getCause() : e, -1); + return InterceptorUtils.getDefaultReturnValue(paramMethodInvocation); + } + } + + /** + * Handles the connection failure. + * + * @param paramMethodInvocation + * {@link MethodInvocation}. + * @param e + * {@link Throwable}. + */ + private void handleConnectionFailure(MethodInvocation paramMethodInvocation, Throwable e) { + CmrRepositoryDefinition cmrRepositoryDefinition = InterceptorUtils.getRepositoryDefinition(paramMethodInvocation); + if (null != cmrRepositoryDefinition) { + if (cmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.ONLINE) { + InspectIT.getDefault().getCmrRepositoryManager().forceCmrRepositoryOnlineStatusUpdate(cmrRepositoryDefinition); + } + InspectIT.getDefault().createErrorDialog("The server: '" + cmrRepositoryDefinition.getIp() + ":" + cmrRepositoryDefinition.getPort() + "' is currenlty unavailable.", e, -1); + } else { + throw new RuntimeException("Service proxy not bounded to the CMR repository definition"); + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/AbstractStorageService.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/AbstractStorageService.java new file mode 100644 index 000000000..fbd354ea0 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/AbstractStorageService.java @@ -0,0 +1,467 @@ +package info.novatec.inspectit.rcp.repository.service.storage; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.indexing.aggregation.IAggregator; +import info.novatec.inspectit.indexing.aggregation.impl.AggregationPerformer; +import info.novatec.inspectit.indexing.storage.IStorageDescriptor; +import info.novatec.inspectit.indexing.storage.IStorageTreeComponent; +import info.novatec.inspectit.indexing.storage.impl.StorageIndexQuery; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.StorageRepositoryDefinition; +import info.novatec.inspectit.rcp.storage.util.DataRetriever; +import info.novatec.inspectit.storage.LocalStorageData; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.StorageManager; +import info.novatec.inspectit.storage.serializer.SerializationException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; + +/** + * Abstract class for all storage services. + * + * @author Ivan Senic + * + * @param + * Type of data provided by the service. + */ +public abstract class AbstractStorageService { + + /** + * Default amount of data that will be requested by one HTTP request. 10MB. + */ + private static final int MAX_QUERY_SIZE = 1024 * 1024 * 10; + + /** + * Storage repository definition. + */ + private StorageRepositoryDefinition storageRepositoryDefinition; + + /** + * {@link LocalStorageData}. + */ + private LocalStorageData localStorageData; + + /** + * {@link DataRetriever}. + */ + private DataRetriever dataRetriever; + + /** + * {@link StorageManager}. + */ + private StorageManager storageManager; + + /** + * Returns the indexing tree that can be used for querying. + * + * @return Returns the indexing tree that can be used for querying. + */ + protected abstract IStorageTreeComponent getIndexingTree(); + + /** + * Executes the query on the indexing tree. + * + * @param storageIndexQuery + * Index query to execute. + * + * @return Result list. + */ + protected List executeQuery(StorageIndexQuery storageIndexQuery) { + return this.executeQuery(storageIndexQuery, null, null, -1); + } + + /** + * Executes the query on the indexing tree. If the {@link IAggregator} is not null + * then the results will be aggregated based on the given {@link IAggregator}. + * + * @param storageIndexQuery + * Index query to execute. + * @param aggregator + * {@link IAggregator}. Pass null if no aggregation is needed. + * @return Result list. + */ + protected List executeQuery(StorageIndexQuery storageIndexQuery, IAggregator aggregator) { + return this.executeQuery(storageIndexQuery, aggregator, null, -1); + } + + /** + * Executes the query on the indexing tree. Results can be sorted by comparator. + * + * @param storageIndexQuery + * Index query to execute. + * @param comparator + * If supplied the final result list will be sorted by this comparator. + * @return Result list. + */ + protected List executeQuery(StorageIndexQuery storageIndexQuery, Comparator comparator) { + return this.executeQuery(storageIndexQuery, null, comparator, -1); + } + + /** + * Executes the query on the indexing tree. Furthermore the result list can be limited. + * + * @param storageIndexQuery + * Index query to execute. + * @param limit + * Limit the number of results by given number. Value -1 means no limit. + * @return Result list. + */ + protected List executeQuery(StorageIndexQuery storageIndexQuery, int limit) { + return this.executeQuery(storageIndexQuery, null, null, limit); + } + + /** + * Executes the query on the indexing tree. If the {@link IAggregator} is not null + * then the results will be aggregated based on the given {@link IAggregator}. + * + * @param storageIndexQuery + * Index query to execute. + * @param aggregator + * {@link IAggregator}. Pass null if no aggregation is needed. + * @param comparator + * If supplied the final result list will be sorted by this comparator. + * @return Result list. + */ + protected List executeQuery(StorageIndexQuery storageIndexQuery, IAggregator aggregator, Comparator comparator) { + return this.executeQuery(storageIndexQuery, aggregator, comparator, -1); + } + + /** + * Executes the query on the indexing tree. If the {@link IAggregator} is not null + * then the results will be aggregated based on the given {@link IAggregator}. Furthermore the + * result list can be limited. + * + * @param storageIndexQuery + * Index query to execute. + * @param aggregator + * {@link IAggregator}. Pass null if no aggregation is needed. + * @param limit + * Limit the number of results by given number. Value -1 means no limit. + * @return Result list. + */ + protected List executeQuery(StorageIndexQuery storageIndexQuery, IAggregator aggregator, int limit) { + return this.executeQuery(storageIndexQuery, aggregator, null, limit); + } + + /** + * Executes the query on the indexing tree. Results can be sorted by comparator. Furthermore the + * result list can be limited. + * + * @param storageIndexQuery + * Index query to execute. + * + * @param comparator + * If supplied the final result list will be sorted by this comparator. + * @param limit + * Limit the number of results by given number. Value -1 means no limit. + * @return Result list. + */ + protected List executeQuery(StorageIndexQuery storageIndexQuery, Comparator comparator, int limit) { + return this.executeQuery(storageIndexQuery, null, comparator, limit); + } + + /** + * This method executes the query in way that it first checks if wanted data is already cached. + * If not method has the ability to load the data via the HTTP or locally and aggregate the data + * if the {@link IAggregator} is provided. If the {@link IAggregator} is not provided, the data + * will be returned not aggregated. + *

+ * In addition it will try to cache the results if they are not yet cached. + *

+ * This method should be used by all subclasses, because it guards against massive data loading + * that can make out of memory exceptions on the UI. + * + * @param storageIndexQuery + * Query. + * @param aggregator + * {@link IAggregator} + * @param comparator + * If supplied the final result list will be sorted by this comparator. + * @param limit + * Limit the number of results by given number. Value -1 means no limit. + * @return Return results of a query. + */ + protected List executeQuery(StorageIndexQuery storageIndexQuery, IAggregator aggregator, Comparator comparator, int limit) { + List returnList = null; + // check if this can be cached + if (storageManager.canBeCached(storageIndexQuery, aggregator)) { + int hash = storageManager.getCachedDataHash(storageIndexQuery, aggregator); + if (!localStorageData.isFullyDownloaded()) { + // check if it s cached on the CMR + StorageData storageData = new StorageData(localStorageData); + try { + returnList = dataRetriever.getCachedDataViaHttp(getCmrRepositoryDefinition(), storageData, hash); + } catch (StorageException | IOException | SerializationException e) { // NOPMD NOCHK + // ignore cause we can still load results in other way + } + + if (null == returnList) { + // if not we load data regular way + returnList = loadData(storageIndexQuery, aggregator); + + // and cache it on the CMR if we get something + if (CollectionUtils.isNotEmpty(returnList)) { + cacheQueryResultOnCmr(getCmrRepositoryDefinition(), storageData, returnList, hash); + } + } + } else { + try { + returnList = dataRetriever.getCachedDataLocally(localStorageData, hash); + } catch (IOException | SerializationException e) { // NOPMD NOCHK + // ignore cause we can still load results in other way + } + + if (null == returnList) { + // if not we load data regular way + returnList = loadData(storageIndexQuery, aggregator); + + // and cache it locally if we get something + if (CollectionUtils.isNotEmpty(returnList)) { + cacheQueryResultLocally(localStorageData, returnList, hash); + } + } + } + } else { + returnList = loadData(storageIndexQuery, aggregator); + } + + // sort if needed + if (null != comparator) { + Collections.sort(returnList, comparator); + } + + // limit the size if needed + if (limit > -1 && returnList.size() > limit) { + returnList = returnList.subList(0, limit); + } + + return returnList; + } + + /** + * Caches result set on the CMR for the given storage under given hash. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to cache results on. + * @param storageData + * {@link StorageData} + * @param results + * Results to cache + * @param hash + * Hash to use + */ + private void cacheQueryResultOnCmr(CmrRepositoryDefinition cmrRepositoryDefinition, StorageData storageData, List results, int hash) { + try { + cmrRepositoryDefinition.getStorageService().cacheStorageData(storageData, results, hash); + } catch (StorageException e) { // NOPMD NOCHK + // ignore also if caching fails + } + } + + /** + * Caches result locally for the given storage under given hash. + * + * @param localStorageData + * {@link LocalStorageData} + * @param results + * Results to cache + * @param hash + * Hash to use + */ + private void cacheQueryResultLocally(LocalStorageData localStorageData, List results, int hash) { + try { + storageManager.cacheStorageData(localStorageData, results, hash); + } catch (IOException | SerializationException e) { // NOPMD NOCHK + // ignore also if caching fails + } + + } + + /** + * This method has the ability to load the data via the HTTP and aggregate the data if the + * {@link IAggregator} is provided. If the {@link IAggregator} is not provided, the data will be + * returned not aggregated. + *

+ * This method should be used by all subclasses, because it guards against massive data loading + * that can make out of memory exceptions on the UI. + * + * @param storageIndexQuery + * Query. + * @param aggregator + * {@link IAggregator} + * @return Return results of a query. + */ + private List loadData(StorageIndexQuery storageIndexQuery, IAggregator aggregator) { + List descriptors = getIndexingTree().query(storageIndexQuery); + // sort the descriptors to optimize the number of read operations + Collections.sort(descriptors, new Comparator() { + @Override + public int compare(IStorageDescriptor o1, IStorageDescriptor o2) { + int channelCompare = Integer.compare(o1.getChannelId(), o2.getChannelId()); + if (channelCompare != 0) { + return channelCompare; + } else { + return Long.compare(o1.getPosition(), o2.getPosition()); + } + } + }); + + AggregationPerformer aggregationPerformer = null; + if (null != aggregator) { + aggregationPerformer = new AggregationPerformer(aggregator); + } + List returnList = new ArrayList(); + + int size = 0; + int count = 0; + List limitedDescriptors = new ArrayList(); + for (IStorageDescriptor storageDescriptor : descriptors) { + // increase count, add descriptor size and update current list + count++; + size += storageDescriptor.getSize(); + limitedDescriptors.add(storageDescriptor); + + // if the size is already to big, or we reached end do query + if (size > MAX_QUERY_SIZE || count == descriptors.size()) { + // load data and filter with restrictions + List allData; + if (localStorageData.isFullyDownloaded()) { + try { + allData = dataRetriever.getDataLocally(localStorageData, descriptors); + } catch (SerializationException e) { + String msg = "Data in the downloaded storage " + localStorageData + " can not be loaded with this version of the inspectIT. Version of the CMR where storage was created is " + + localStorageData.getCmrVersion() + "."; + InspectIT.getDefault().createErrorDialog(msg, e, -1); + return Collections.emptyList(); + } catch (IOException e) { + InspectIT.getDefault().createErrorDialog("Exception occurred trying to load the data.", e, -1); + return Collections.emptyList(); + } + } else { + try { + allData = dataRetriever.getDataViaHttp(getCmrRepositoryDefinition(), localStorageData, limitedDescriptors); + } catch (SerializationException e) { + String msg = "Data in the remote storage " + localStorageData + " can not be loaded with this version of the inspectIT. Version of the CMR where storage was created is " + + localStorageData.getCmrVersion() + "."; + InspectIT.getDefault().createErrorDialog(msg, e, -1); + return Collections.emptyList(); + } catch (IOException e) { + InspectIT.getDefault().createErrorDialog("Exception occurred trying to load the data.", e, -1); + return Collections.emptyList(); + } + } + List passedData = getRestrictionsPassedList(allData, storageIndexQuery); + + // if we need to aggregate then do so, otherwise just add to result list + if (null != aggregationPerformer) { + aggregationPerformer.processCollection(passedData); + } else { + returnList.addAll(passedData); + } + + // reset the size and current list + size = 0; + limitedDescriptors.clear(); + } + } + + // aggregate if needed + if (null != aggregator) { + returnList = aggregationPerformer.getResultList(); + } + + return returnList; + } + + /** + * This utility method is used to create a list of elements that pass all the restrictions in + * the {@link StorageIndexQuery}. + * + * @param notPassedList + * List of all elements. + * @param storageIndexQuery + * {@link StorageIndexQuery}. + * @return New list only with elements that are passing all restrictions. + */ + private List getRestrictionsPassedList(List notPassedList, StorageIndexQuery storageIndexQuery) { + List passedList = new ArrayList(); + for (E element : notPassedList) { + if (null != element && element.isQueryComplied(storageIndexQuery)) { + passedList.add(element); + } + } + return passedList; + } + + /** + * Gets {@link #storageRepositoryDefinition}. + * + * @return {@link #storageRepositoryDefinition} + */ + public StorageRepositoryDefinition getStorageRepositoryDefinition() { + return storageRepositoryDefinition; + } + + /** + * Sets {@link #storageRepositoryDefinition}. + * + * @param storageRepositoryDefinition + * New value for {@link #storageRepositoryDefinition} + */ + public void setStorageRepositoryDefinition(StorageRepositoryDefinition storageRepositoryDefinition) { + this.storageRepositoryDefinition = storageRepositoryDefinition; + } + + /** + * @return the cmrRepositoryDefinition + */ + public CmrRepositoryDefinition getCmrRepositoryDefinition() { + return getStorageRepositoryDefinition().getCmrRepositoryDefinition(); + } + + /** + * Gets {@link #localStorageData}. + * + * @return {@link #localStorageData} + */ + public LocalStorageData getLocalStorageData() { + return localStorageData; + } + + /** + * Sets {@link #localStorageData}. + * + * @param localStorageData + * New value for {@link #localStorageData} + */ + public void setLocalStorageData(LocalStorageData localStorageData) { + this.localStorageData = localStorageData; + } + + /** + * @param dataRetriever + * the httpDataRetriever to set + */ + public void setDataRetriever(DataRetriever dataRetriever) { + this.dataRetriever = dataRetriever; + } + + /** + * Sets {@link #storageManager}. + * + * @param storageManager + * New value for {@link #storageManager} + */ + public void setStorageManager(StorageManager storageManager) { + this.storageManager = storageManager; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageExceptionDataAccessService.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageExceptionDataAccessService.java new file mode 100644 index 000000000..39c3ebf4a --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageExceptionDataAccessService.java @@ -0,0 +1,137 @@ +package info.novatec.inspectit.rcp.repository.service.storage; + +import info.novatec.inspectit.cmr.service.IExceptionDataAccessService; +import info.novatec.inspectit.communication.comparator.DefaultDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.communication.data.AggregatedExceptionSensorData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.indexing.aggregation.Aggregators; +import info.novatec.inspectit.indexing.query.factory.impl.ExceptionSensorDataQueryFactory; +import info.novatec.inspectit.indexing.storage.IStorageTreeComponent; +import info.novatec.inspectit.indexing.storage.impl.StorageIndexQuery; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +/** + * {@link IExceptionDataAccessService} for storage purposes. + * + * @author Ivan Senic + * + */ +public class StorageExceptionDataAccessService extends AbstractStorageService implements IExceptionDataAccessService { + + /** + * Indexing tree. + */ + private IStorageTreeComponent indexingTree; + + /** + * Index query provider. + */ + private ExceptionSensorDataQueryFactory exceptionSensorDataQueryFactory; + + /** + * {@inheritDoc} + */ + public List getUngroupedExceptionOverview(ExceptionSensorData template, int limit, ResultComparator resultComparator) { + return this.getUngroupedExceptionOverview(template, limit, null, null, resultComparator); + } + + /** + * {@inheritDoc} + */ + public List getUngroupedExceptionOverview(ExceptionSensorData template, int limit, Date fromDate, Date toDate, ResultComparator resultComparator) { + StorageIndexQuery query = exceptionSensorDataQueryFactory.getUngroupedExceptionOverviewQuery(template, limit, fromDate, toDate); + if (null != resultComparator) { + resultComparator.setCachedDataService(getStorageRepositoryDefinition().getCachedDataService()); + return super.executeQuery(query, resultComparator, limit); + } else { + return super.executeQuery(query, DefaultDataComparatorEnum.TIMESTAMP, limit); + } + } + + /** + * {@inheritDoc} + */ + public List getUngroupedExceptionOverview(ExceptionSensorData template, ResultComparator resultComparator) { + return this.getUngroupedExceptionOverview(template, -1, null, null, resultComparator); + } + + /** + * {@inheritDoc} + */ + public List getUngroupedExceptionOverview(ExceptionSensorData template, Date fromDate, Date toDate, ResultComparator resultComparator) { + return this.getUngroupedExceptionOverview(template, -1, fromDate, toDate, resultComparator); + } + + /** + * {@inheritDoc} + */ + public List getExceptionTree(ExceptionSensorData template) { + // here we have a problem because we have to de-serialize every exception to find the right + // one, we need to check if we can change this method + StorageIndexQuery query = exceptionSensorDataQueryFactory.getExceptionTreeQuery(template); + List results = super.executeQuery(query); + Collections.reverse(results); + return results; + } + + /** + * {@inheritDoc} + */ + public List getDataForGroupedExceptionOverview(ExceptionSensorData template) { + return this.getDataForGroupedExceptionOverview(template, null, null); + } + + /** + * {@inheritDoc} + */ + public List getDataForGroupedExceptionOverview(ExceptionSensorData template, Date fromDate, Date toDate) { + StorageIndexQuery query = exceptionSensorDataQueryFactory.getDataForGroupedExceptionOverviewQuery(template, fromDate, toDate); + List resultList = super.executeQuery(query, Aggregators.GROUP_EXCEPTION_OVERVIEW_AGGREGATOR); + List filterList = new ArrayList(resultList.size()); + for (ExceptionSensorData data : resultList) { + if (data instanceof AggregatedExceptionSensorData) { + filterList.add((AggregatedExceptionSensorData) data); + } + } + return filterList; + } + + /** + * {@inheritDoc} + */ + @Override + public List getStackTraceMessagesForThrowableType(ExceptionSensorData template) { + // same problem again, we need to de-serialize all exceptions + StorageIndexQuery query = exceptionSensorDataQueryFactory.getStackTraceMessagesForThrowableTypeQuery(template); + return super.executeQuery(query, Aggregators.DISTINCT_STACK_TRACES_AGGREGATOR); + } + + /** + * {@inheritDoc} + */ + protected IStorageTreeComponent getIndexingTree() { + return indexingTree; + } + + /** + * @param indexingTree + * the indexingTree to set + */ + public void setIndexingTree(IStorageTreeComponent indexingTree) { + this.indexingTree = indexingTree; + } + + /** + * @param exceptionSensorDataQueryFactory + * the exceptionSensorDataQueryFactory to set + */ + public void setExceptionSensorDataQueryFactory(ExceptionSensorDataQueryFactory exceptionSensorDataQueryFactory) { + this.exceptionSensorDataQueryFactory = exceptionSensorDataQueryFactory; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageGlobalDataAccessService.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageGlobalDataAccessService.java new file mode 100644 index 000000000..692081b83 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageGlobalDataAccessService.java @@ -0,0 +1,251 @@ +package info.novatec.inspectit.rcp.repository.service.storage; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.cmr.service.IGlobalDataAccessService; +import info.novatec.inspectit.cmr.service.exception.ServiceException; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.cmr.AgentStatusData; +import info.novatec.inspectit.indexing.query.provider.impl.StorageIndexQueryProvider; +import info.novatec.inspectit.indexing.storage.IStorageTreeComponent; +import info.novatec.inspectit.indexing.storage.impl.StorageIndexQuery; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections.CollectionUtils; + +/** + * {@link IGlobalDataAccessService} for storage purposes. This class indirectly uses the + * {@link AbstractCachedGlobalDataAccessService} to cache the data. + * + * @author Ivan Senic + * + */ +public class StorageGlobalDataAccessService extends AbstractStorageService implements IGlobalDataAccessService { + + /** + * List of agents. + */ + private List agents; + + /** + * Indexing tree. + */ + private IStorageTreeComponent indexingTree; + + /** + * {@link StorageIndexQueryProvider}. + */ + private StorageIndexQueryProvider storageIndexQueryProvider; + + /** + * {@inheritDoc} + */ + public Map getAgentsOverview() { + Map result = new HashMap(); + for (PlatformIdent platformIdent : agents) { + result.put(platformIdent, null); + } + return result; + } + + /** + * {@inheritDoc} + */ + public PlatformIdent getCompleteAgent(long id) throws ServiceException { + for (PlatformIdent platformIdent : agents) { + if (platformIdent.getId().longValue() == id) { + return platformIdent; + } + } + throw new ServiceException("Agent with given ID=" + id + " is not existing."); + } + + /** + * {@inheritDoc} + *

+ * Agents can not be deleted on the Storage. + */ + public void deleteAgent(long platformId) throws ServiceException { + } + + /** + * {@inheritDoc} + */ + public List getLastDataObjects(DefaultData template, long timeInterval) { + Timestamp toDate = new Timestamp(new Date().getTime()); + Timestamp fromDate = new Timestamp(toDate.getTime() - timeInterval); + return this.getDataObjectsInInterval(template, fromDate, toDate); + } + + /** + * {@inheritDoc} + */ + public DefaultData getLastDataObject(DefaultData template) { + StorageIndexQuery query = storageIndexQueryProvider.createNewStorageIndexQuery(); + query.setMinId(template.getId()); + ArrayList> searchClasses = new ArrayList>(); + searchClasses.add(template.getClass()); + query.setObjectClasses(searchClasses); + query.setPlatformIdent(template.getPlatformIdent()); + query.setSensorTypeIdent(template.getSensorTypeIdent()); + List resultList = super.executeQuery(query); + + if (CollectionUtils.isNotEmpty(resultList)) { + Iterator it = resultList.iterator(); + DefaultData lastObject = it.next(); + while (it.hasNext()) { + DefaultData next = it.next(); + if (next.getTimeStamp().after(lastObject.getTimeStamp())) { + lastObject = next; + } + } + return lastObject; + } else { + return null; + } + } + + /** + * {@inheritDoc} + */ + public List getDataObjectsSinceId(DefaultData template) { + StorageIndexQuery query = storageIndexQueryProvider.createNewStorageIndexQuery(); + query.setMinId(template.getId()); + ArrayList> searchClasses = new ArrayList>(); + searchClasses.add(template.getClass()); + query.setObjectClasses(searchClasses); + query.setPlatformIdent(template.getPlatformIdent()); + query.setSensorTypeIdent(template.getSensorTypeIdent()); + if (template instanceof MethodSensorData) { + query.setMethodIdent(((MethodSensorData) template).getMethodIdent()); + if (template instanceof InvocationSequenceData) { + query.setOnlyInvocationsWithoutChildren(true); + } + } + + return super.executeQuery(query); + } + + /** + * {@inheritDoc} + */ + public List getDataObjectsSinceIdIgnoreMethodId(DefaultData template) { + StorageIndexQuery query = storageIndexQueryProvider.createNewStorageIndexQuery(); + query.setMinId(template.getId()); + ArrayList> searchClasses = new ArrayList>(); + searchClasses.add(template.getClass()); + query.setObjectClasses(searchClasses); + query.setPlatformIdent(template.getPlatformIdent()); + + return super.executeQuery(query); + } + + /** + * {@inheritDoc} + */ + public List getDataObjectsFromToDate(DefaultData template, Date fromDate, Date toDate) { + if (fromDate.after(toDate)) { + return Collections.emptyList(); + } + + return this.getDataObjectsInInterval(template, new Timestamp(fromDate.getTime()), new Timestamp(toDate.getTime())); + } + + /** + * {@inheritDoc} + */ + @Override + public List getTemplatesDataObjectsFromToDate(Collection templates, Date fromDate, Date toDate) { + if (fromDate.after(toDate)) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(); + for (DefaultData template : templates) { + result.addAll(this.getDataObjectsFromToDate(template, fromDate, toDate)); + } + return result; + } + + /** + * Returns data objects in wanted interval based on the wanted template. + * + * @param template + * Template to base search on. + * @param fromDate + * From date as Timestamp. + * @param toDate + * To date as Timestamp. + * @return List of {@link DefaultData} objects. + */ + private List getDataObjectsInInterval(DefaultData template, Timestamp fromDate, Timestamp toDate) { + StorageIndexQuery query = storageIndexQueryProvider.createNewStorageIndexQuery(); + ArrayList> searchClasses = new ArrayList>(); + searchClasses.add(template.getClass()); + query.setObjectClasses(searchClasses); + query.setPlatformIdent(template.getPlatformIdent()); + query.setSensorTypeIdent(template.getSensorTypeIdent()); + query.setToDate(toDate); + query.setFromDate(fromDate); + if (template instanceof MethodSensorData) { + query.setMethodIdent(((MethodSensorData) template).getMethodIdent()); + if (template instanceof InvocationSequenceData) { + query.setOnlyInvocationsWithoutChildren(true); + } + } + + List returnList = super.executeQuery(query); + Collections.sort(returnList, new Comparator() { + + @Override + public int compare(DefaultData o1, DefaultData o2) { + return o1.getTimeStamp().compareTo(o2.getTimeStamp()); + } + }); + + return returnList; + } + + /** + * @param agents + * the agents to set + */ + public void setAgents(List agents) { + this.agents = agents; + } + + /** + * {@inheritDoc} + */ + protected IStorageTreeComponent getIndexingTree() { + return indexingTree; + } + + /** + * @param indexingTree + * the indexingTree to set + */ + public void setIndexingTree(IStorageTreeComponent indexingTree) { + this.indexingTree = indexingTree; + } + + /** + * @param storageIndexQueryProvider + * the storageIndexQueryProvider to set + */ + public void setStorageIndexQueryProvider(StorageIndexQueryProvider storageIndexQueryProvider) { + this.storageIndexQueryProvider = storageIndexQueryProvider; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageHttpTimerDataAccessService.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageHttpTimerDataAccessService.java new file mode 100644 index 000000000..cf1c85e8c --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageHttpTimerDataAccessService.java @@ -0,0 +1,139 @@ +package info.novatec.inspectit.rcp.repository.service.storage; + +import info.novatec.inspectit.cmr.service.IHttpTimerDataAccessService; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.indexing.aggregation.impl.HttpTimerDataAggregator; +import info.novatec.inspectit.indexing.query.factory.impl.HttpTimerDataQueryFactory; +import info.novatec.inspectit.indexing.restriction.impl.IndexQueryRestrictionFactory; +import info.novatec.inspectit.indexing.storage.IStorageTreeComponent; +import info.novatec.inspectit.indexing.storage.impl.StorageIndexQuery; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.collections.CollectionUtils; + +/** + * {@link IHttpTimerDataAccessService} for storage purposes. + * + * @author Ivan Senic + * + */ +public class StorageHttpTimerDataAccessService extends AbstractStorageService implements IHttpTimerDataAccessService { + + /** + * Comparator used to compare on the timestamp. + */ + private static final Comparator TIMESTAMP_COMPARATOR = new Comparator() { + + @Override + public int compare(HttpTimerData o1, HttpTimerData o2) { + return ObjectUtils.compare(o1.getTimeStamp(), o2.getTimeStamp()); + } + + }; + + /** + * Indexing tree. + */ + private IStorageTreeComponent indexingTree; + + /** + * Index query provider. + */ + private HttpTimerDataQueryFactory httpDataQueryFactory; + + /** + * {@inheritDoc} + */ + public List getAggregatedTimerData(HttpTimerData httpData, boolean includeRequestMethod) { + StorageIndexQuery query = httpDataQueryFactory.getFindAllHttpTimersQuery(httpData, null, null); + return super.executeQuery(query, new HttpTimerDataAggregator(true, includeRequestMethod)); + } + + /** + * {@inheritDoc} + */ + public List getAggregatedTimerData(HttpTimerData httpData, boolean includeRequestMethod, Date fromDate, Date toDate) { + StorageIndexQuery query = httpDataQueryFactory.getFindAllHttpTimersQuery(httpData, fromDate, toDate); + return super.executeQuery(query, new HttpTimerDataAggregator(true, includeRequestMethod)); + } + + /** + * {@inheritDoc} + */ + public List getTaggedAggregatedTimerData(HttpTimerData httpData, boolean includeRequestMethod) { + StorageIndexQuery query = httpDataQueryFactory.getFindAllTaggedHttpTimersQuery(httpData, null, null); + return super.executeQuery(query, new HttpTimerDataAggregator(false, includeRequestMethod)); + } + + /** + * {@inheritDoc} + */ + public List getTaggedAggregatedTimerData(HttpTimerData httpData, boolean includeRequestMethod, Date fromDate, Date toDate) { + StorageIndexQuery query = httpDataQueryFactory.getFindAllTaggedHttpTimersQuery(httpData, fromDate, toDate); + return super.executeQuery(query, new HttpTimerDataAggregator(false, includeRequestMethod)); + } + + /** + * {@inheritDoc} + */ + public List getChartingHttpTimerDataFromDateToDate(Collection templates, Date fromDate, Date toDate, boolean retrieveByTag) { + if (CollectionUtils.isNotEmpty(templates)) { + StorageIndexQuery query = httpDataQueryFactory.getFindAllHttpTimersQuery(templates.iterator().next(), fromDate, toDate); + + if (!retrieveByTag) { + Set uris = new HashSet(); + for (HttpTimerData httpTimerData : templates) { + if (!HttpTimerData.UNDEFINED.equals(httpTimerData.getUri())) { + uris.add(httpTimerData.getUri()); + } + } + query.addIndexingRestriction(IndexQueryRestrictionFactory.isInCollection("uri", uris)); + } else { + Set tags = new HashSet(); + + for (HttpTimerData httpTimerData : templates) { + if (httpTimerData.hasInspectItTaggingHeader()) { + tags.add(httpTimerData.getInspectItTaggingHeaderValue()); + } + } + query.addIndexingRestriction(IndexQueryRestrictionFactory.isInCollection("inspectItTaggingHeaderValue", tags)); + } + + return super.executeQuery(query, TIMESTAMP_COMPARATOR); + } else { + return Collections.emptyList(); + } + } + + /** + * {@inheritDoc} + */ + protected IStorageTreeComponent getIndexingTree() { + return indexingTree; + } + + /** + * @param indexingTree + * the indexingTree to set + */ + public void setIndexingTree(IStorageTreeComponent indexingTree) { + this.indexingTree = indexingTree; + } + + /** + * @param httpDataQueryFactory + * the httpDataQueryFactory to set + */ + public void setHttpDataQueryFactory(HttpTimerDataQueryFactory httpDataQueryFactory) { + this.httpDataQueryFactory = httpDataQueryFactory; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageInvocationDataAccessService.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageInvocationDataAccessService.java new file mode 100644 index 000000000..9c113cfc9 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageInvocationDataAccessService.java @@ -0,0 +1,130 @@ +package info.novatec.inspectit.rcp.repository.service.storage; + +import info.novatec.inspectit.cmr.service.IInvocationDataAccessService; +import info.novatec.inspectit.communication.comparator.DefaultDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.indexing.query.factory.impl.InvocationSequenceDataQueryFactory; +import info.novatec.inspectit.indexing.storage.IStorageTreeComponent; +import info.novatec.inspectit.indexing.storage.impl.StorageIndexQuery; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + * {@link IInvocationDataAccessService} for storage purposes. + * + * @author Ivan Senic + * + */ +public class StorageInvocationDataAccessService extends AbstractStorageService implements IInvocationDataAccessService { + + /** + * Indexing tree. + */ + private IStorageTreeComponent indexingTree; + + /** + * Index query provider. + */ + private InvocationSequenceDataQueryFactory invocationDataQueryFactory; + + /** + * {@inheritDoc} + */ + public List getInvocationSequenceOverview(long platformId, long methodId, int limit, ResultComparator resultComparator) { + return this.getInvocationSequenceOverview(platformId, methodId, limit, null, null, resultComparator); + } + + /** + * {@inheritDoc} + */ + public List getInvocationSequenceOverview(long platformId, int limit, ResultComparator resultComparator) { + return this.getInvocationSequenceOverview(platformId, 0, limit, resultComparator); + } + + /** + * {@inheritDoc} + */ + public List getInvocationSequenceOverview(long platformId, long methodId, int limit, Date fromDate, Date toDate, ResultComparator resultComparator) { + StorageIndexQuery query = invocationDataQueryFactory.getInvocationSequenceOverview(platformId, methodId, limit, fromDate, toDate); + query.setOnlyInvocationsWithoutChildren(true); + if (null != resultComparator) { + resultComparator.setCachedDataService(getStorageRepositoryDefinition().getCachedDataService()); + return super.executeQuery(query, resultComparator, limit); + } else { + return super.executeQuery(query, DefaultDataComparatorEnum.TIMESTAMP, limit); + } + + } + + /** + * {@inheritDoc} + */ + public List getInvocationSequenceOverview(long platformId, int limit, Date fromDate, Date toDate, ResultComparator resultComparator) { + return this.getInvocationSequenceOverview(platformId, 0, limit, fromDate, toDate, resultComparator); + } + + /** + * {@inheritDoc} + */ + public List getInvocationSequenceOverview(long platformId, Collection invocationIdCollection, int limit, ResultComparator resultComparator) { + StorageIndexQuery query = invocationDataQueryFactory.getInvocationSequenceOverview(platformId, invocationIdCollection, limit); + query.setOnlyInvocationsWithoutChildren(true); + if (null != resultComparator) { + resultComparator.setCachedDataService(getStorageRepositoryDefinition().getCachedDataService()); + return super.executeQuery(query, resultComparator, limit); + } else { + return super.executeQuery(query, DefaultDataComparatorEnum.TIMESTAMP, limit); + } + } + + /** + * {@inheritDoc} + */ + public InvocationSequenceData getInvocationSequenceDetail(InvocationSequenceData template) { + // here we need to create new query since this one does not exist in factory + StorageIndexQuery query = invocationDataQueryFactory.getIndexQueryProvider().getIndexQuery(); + ArrayList> searchedClasses = new ArrayList>(); + searchedClasses.add(InvocationSequenceData.class); + query.setObjectClasses(searchedClasses); + query.setPlatformIdent(template.getPlatformIdent()); + query.setMethodIdent(template.getMethodIdent()); + query.setSensorTypeIdent(template.getSensorTypeIdent()); + query.setOnlyInvocationsWithoutChildren(false); + ArrayList includeIds = new ArrayList(); + includeIds.add(template.getId()); + query.setIncludeIds(includeIds); + List results = super.executeQuery(query); + if (results.size() == 1) { + return results.get(0); + } + return null; + } + + /** + * {@inheritDoc} + */ + protected IStorageTreeComponent getIndexingTree() { + return indexingTree; + } + + /** + * @param indexingTree + * the indexingTree to set + */ + public void setIndexingTree(IStorageTreeComponent indexingTree) { + this.indexingTree = indexingTree; + } + + /** + * @param invocationDataQueryFactory + * the invocationDataQueryFactory to set + */ + public void setInvocationDataQueryFactory(InvocationSequenceDataQueryFactory invocationDataQueryFactory) { + this.invocationDataQueryFactory = invocationDataQueryFactory; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageServiceProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageServiceProvider.java new file mode 100644 index 000000000..71ae1ba4d --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageServiceProvider.java @@ -0,0 +1,178 @@ +package info.novatec.inspectit.rcp.repository.service.storage; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.indexing.storage.IStorageTreeComponent; +import info.novatec.inspectit.rcp.repository.StorageRepositoryDefinition; +import info.novatec.inspectit.storage.LocalStorageData; + +import java.util.List; + +/** + * Provider of all storage related services. This classes correctly initialize the service with help + * of Spring. + * + * @author Ivan Senic + * + */ +public abstract class StorageServiceProvider { + + /** + * @return Spring created {@link StorageTimerDataAccessService}. + */ + protected abstract StorageTimerDataAccessService createStorageTimerDataAccessService(); + + /** + * Properly initialized {@link StorageTimerDataAccessService}. + * + * @param storageRepositoryDefinition + * {@link StorageRepositoryDefinition}. + * @param localStorageData + * {@link LocalStorageData}. + * @param storageTreeComponent + * Indexing tree. + * @return Properly initialized {@link StorageTimerDataAccessService}. + */ + public StorageTimerDataAccessService createStorageTimerDataAccessService(StorageRepositoryDefinition storageRepositoryDefinition, LocalStorageData localStorageData, + IStorageTreeComponent storageTreeComponent) { + StorageTimerDataAccessService storageTimerDataService = createStorageTimerDataAccessService(); + storageTimerDataService.setStorageRepositoryDefinition(storageRepositoryDefinition); + storageTimerDataService.setLocalStorageData(localStorageData); + storageTimerDataService.setIndexingTree(storageTreeComponent); + return storageTimerDataService; + } + + /** + * @return Spring created {@link StorageHttpTimerDataAccessService}. + */ + protected abstract StorageHttpTimerDataAccessService createStorageHttpTimerDataAccessService(); + + /** + * Properly initialized {@link StorageHttpTimerDataAccessService}. + * + * @param storageRepositoryDefinition + * {@link StorageRepositoryDefinition}. + * @param localStorageData + * {@link LocalStorageData}. + * @param storageTreeComponent + * Indexing tree. + * @return Properly initialized {@link StorageHttpTimerDataAccessService}. + */ + public StorageHttpTimerDataAccessService createStorageHttpTimerDataAccessService(StorageRepositoryDefinition storageRepositoryDefinition, LocalStorageData localStorageData, + IStorageTreeComponent storageTreeComponent) { + StorageHttpTimerDataAccessService storageHttpTimerDataService = createStorageHttpTimerDataAccessService(); + storageHttpTimerDataService.setStorageRepositoryDefinition(storageRepositoryDefinition); + storageHttpTimerDataService.setLocalStorageData(localStorageData); + storageHttpTimerDataService.setIndexingTree(storageTreeComponent); + return storageHttpTimerDataService; + } + + /** + * @return Spring created {@link StorageSqlDataAccessService}. + */ + protected abstract StorageSqlDataAccessService createStorageSqlDataAccessService(); + + /** + * Properly initialized {@link StorageSqlDataAccessService}. + * + * @param storageRepositoryDefinition + * {@link StorageRepositoryDefinition}. + * @param localStorageData + * {@link LocalStorageData}. + * @param storageTreeComponent + * Indexing tree. + * @return Properly initialized {@link StorageSqlDataAccessService}. + */ + public StorageSqlDataAccessService createStorageSqlDataAccessService(StorageRepositoryDefinition storageRepositoryDefinition, LocalStorageData localStorageData, + IStorageTreeComponent storageTreeComponent) { + StorageSqlDataAccessService storageSqlDataAccessService = createStorageSqlDataAccessService(); + storageSqlDataAccessService.setStorageRepositoryDefinition(storageRepositoryDefinition); + storageSqlDataAccessService.setLocalStorageData(localStorageData); + storageSqlDataAccessService.setIndexingTree(storageTreeComponent); + return storageSqlDataAccessService; + } + + /** + * @return Spring created {@link StorageExceptionDataAccessService}. + */ + protected abstract StorageExceptionDataAccessService createStorageExceptionDataAccessService(); + + /** + * Properly initialized {@link StorageExceptionDataAccessService}. + * + * @param storageRepositoryDefinition + * {@link StorageRepositoryDefinition}. + * @param localStorageData + * {@link LocalStorageData}. + * @param storageTreeComponent + * Indexing tree. + * @return Properly initialized {@link StorageExceptionDataAccessService}. + */ + public StorageExceptionDataAccessService createStorageExceptionDataAccessService(StorageRepositoryDefinition storageRepositoryDefinition, LocalStorageData localStorageData, + IStorageTreeComponent storageTreeComponent) { + StorageExceptionDataAccessService storageExceptionDataAccessService = createStorageExceptionDataAccessService(); + storageExceptionDataAccessService.setStorageRepositoryDefinition(storageRepositoryDefinition); + storageExceptionDataAccessService.setLocalStorageData(localStorageData); + storageExceptionDataAccessService.setIndexingTree(storageTreeComponent); + return storageExceptionDataAccessService; + } + + /** + * @return Spring created {@link StorageInvocationDataAccessService}. + */ + protected abstract StorageInvocationDataAccessService createStorageInvocationDataAccessService(); + + /** + * Properly initialized {@link StorageInvocationDataAccessService}. + * + * @param storageRepositoryDefinition + * {@link StorageRepositoryDefinition}. + * @param localStorageData + * {@link LocalStorageData}. + * @param storageTreeComponent + * Indexing tree. + * @param cachedDataSer + * @return Properly initialized {@link StorageInvocationDataAccessService}. + */ + public StorageInvocationDataAccessService createStorageInvocationDataAccessService(StorageRepositoryDefinition storageRepositoryDefinition, LocalStorageData localStorageData, + IStorageTreeComponent storageTreeComponent) { + StorageInvocationDataAccessService storageInvocationDataAccessService = createStorageInvocationDataAccessService(); + storageInvocationDataAccessService.setStorageRepositoryDefinition(storageRepositoryDefinition); + storageInvocationDataAccessService.setLocalStorageData(localStorageData); + storageInvocationDataAccessService.setIndexingTree(storageTreeComponent); + return storageInvocationDataAccessService; + } + + /** + * @return Spring created {@link StorageGlobalDataAccessService}. + */ + protected abstract StorageGlobalDataAccessService createStorageGlobalDataAccessService(); + + /** + * Properly initialized {@link StorageGlobalDataAccessService}. + * + * @param storageRepositoryDefinition + * {@link StorageRepositoryDefinition}. + * @param localStorageData + * {@link LocalStorageData}. + * @param storageTreeComponent + * Indexing tree. + * @param platformIdents + * Agents related to storage. + * @return Properly initialized {@link StorageGlobalDataAccessService}. + */ + public StorageGlobalDataAccessService createStorageGlobalDataAccessService(StorageRepositoryDefinition storageRepositoryDefinition, LocalStorageData localStorageData, + IStorageTreeComponent storageTreeComponent, List platformIdents) { + StorageGlobalDataAccessService storageGlobalDataAccessService = createStorageGlobalDataAccessService(); + storageGlobalDataAccessService.setStorageRepositoryDefinition(storageRepositoryDefinition); + storageGlobalDataAccessService.setLocalStorageData(localStorageData); + storageGlobalDataAccessService.setIndexingTree(storageTreeComponent); + storageGlobalDataAccessService.setAgents(platformIdents); + return storageGlobalDataAccessService; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageSqlDataAccessService.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageSqlDataAccessService.java new file mode 100644 index 000000000..1d7f3eb15 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageSqlDataAccessService.java @@ -0,0 +1,85 @@ +package info.novatec.inspectit.rcp.repository.service.storage; + +import info.novatec.inspectit.cmr.service.ISqlDataAccessService; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.indexing.aggregation.Aggregators; +import info.novatec.inspectit.indexing.query.factory.impl.SqlStatementDataQueryFactory; +import info.novatec.inspectit.indexing.storage.IStorageTreeComponent; +import info.novatec.inspectit.indexing.storage.impl.StorageIndexQuery; + +import java.util.Date; +import java.util.List; + +/** + * {@link ISqlDataAccessService} for storage purposes. + * + * @author Ivan Senic + * + */ +public class StorageSqlDataAccessService extends AbstractStorageService implements ISqlDataAccessService { + + /** + * Indexing tree. + */ + private IStorageTreeComponent indexingTree; + + /** + * Index query provider. + */ + private SqlStatementDataQueryFactory sqlDataQueryFactory; + + /** + * {@inheritDoc} + */ + public List getAggregatedSqlStatements(SqlStatementData sqlStatementData) { + return this.getAggregatedSqlStatements(sqlStatementData, null, null); + } + + /** + * {@inheritDoc} + */ + public List getAggregatedSqlStatements(SqlStatementData sqlStatementData, Date fromDate, Date toDate) { + StorageIndexQuery query = sqlDataQueryFactory.getAggregatedSqlStatementsQuery(sqlStatementData, fromDate, toDate); + return super.executeQuery(query, Aggregators.SQL_STATEMENT_DATA_AGGREGATOR); + } + + /** + * {@inheritDoc} + */ + public List getParameterAggregatedSqlStatements(SqlStatementData sqlStatementData) { + return this.getParameterAggregatedSqlStatements(sqlStatementData, null, null); + } + + /** + * {@inheritDoc} + */ + public List getParameterAggregatedSqlStatements(SqlStatementData sqlStatementData, Date fromDate, Date toDate) { + StorageIndexQuery query = sqlDataQueryFactory.getAggregatedSqlStatementsQuery(sqlStatementData, fromDate, toDate); + query.setSql(sqlStatementData.getSql()); + return super.executeQuery(query, Aggregators.SQL_STATEMENT_DATA_PARAMETER_AGGREGATOR); + } + + /** + * {@inheritDoc} + */ + protected IStorageTreeComponent getIndexingTree() { + return indexingTree; + } + + /** + * @param indexingTree + * the indexingTree to set + */ + public void setIndexingTree(IStorageTreeComponent indexingTree) { + this.indexingTree = indexingTree; + } + + /** + * @param sqlDataQueryFactory + * the sqlDataQueryFactory to set + */ + public void setSqlDataQueryFactory(SqlStatementDataQueryFactory sqlDataQueryFactory) { + this.sqlDataQueryFactory = sqlDataQueryFactory; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageTimerDataAccessService.java b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageTimerDataAccessService.java new file mode 100644 index 000000000..f03e93fa9 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/repository/service/storage/StorageTimerDataAccessService.java @@ -0,0 +1,69 @@ +package info.novatec.inspectit.rcp.repository.service.storage; + +import info.novatec.inspectit.cmr.service.ITimerDataAccessService; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.indexing.aggregation.Aggregators; +import info.novatec.inspectit.indexing.query.factory.impl.TimerDataQueryFactory; +import info.novatec.inspectit.indexing.storage.IStorageTreeComponent; +import info.novatec.inspectit.indexing.storage.impl.StorageIndexQuery; + +import java.util.Date; +import java.util.List; + +/** + * {@link ITimerDataAccessService} for storage purposes. + * + * @author Ivan Senic + * + */ +public class StorageTimerDataAccessService extends AbstractStorageService implements ITimerDataAccessService { + + /** + * Indexing tree. + */ + private IStorageTreeComponent indexingTree; + + /** + * Index query provider. + */ + private TimerDataQueryFactory timerDataQueryFactory; + + /** + * {@inheritDoc} + */ + public List getAggregatedTimerData(TimerData timerData) { + return this.getAggregatedTimerData(timerData, null, null); + } + + /** + * {@inheritDoc} + */ + public List getAggregatedTimerData(TimerData timerData, Date fromDate, Date toDate) { + StorageIndexQuery query = timerDataQueryFactory.getAggregatedTimerDataQuery(timerData, fromDate, toDate); + return super.executeQuery(query, Aggregators.TIMER_DATA_AGGREGATOR); + } + + /** + * {@inheritDoc} + */ + protected IStorageTreeComponent getIndexingTree() { + return indexingTree; + } + + /** + * @param indexingTree + * the indexingTree to set + */ + public void setIndexingTree(IStorageTreeComponent indexingTree) { + this.indexingTree = indexingTree; + } + + /** + * @param timerDataQueryFactory + * the timerDataQueryFactory to set + */ + public void setTimerDataQueryFactory(TimerDataQueryFactory timerDataQueryFactory) { + this.timerDataQueryFactory = timerDataQueryFactory; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/resource/CombinedIcon.java b/inspectIT/src/info/novatec/inspectit/rcp/resource/CombinedIcon.java new file mode 100644 index 000000000..2ab411863 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/resource/CombinedIcon.java @@ -0,0 +1,96 @@ +package info.novatec.inspectit.rcp.resource; + +import java.util.Arrays; + +import org.eclipse.jface.resource.CompositeImageDescriptor; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.Point; + +/** + * Combined icon image descriptor capable of combining several images in one on the vertical or + * horizontal level. + * + * @author Ivan Senic + * + */ +public class CombinedIcon extends CompositeImageDescriptor { + + /** + * {@link ImageDescriptor}s that will be included in the combined icon. + */ + private ImageDescriptor[] descriptors; + + /** + * Size of the combined image. + */ + private Point size; + + /** + * Is vertical combination. + */ + private boolean isVertical; + + /** + * Default constructor. + * + * @param descriptors + * {@link ImageDescriptor}s that will be included in the combined icon. + * @param orientation + * {@link SWT#VERTICAL} or {@link SWT#HORIZONTAL}. Way the images will be pasted. + */ + public CombinedIcon(ImageDescriptor[] descriptors, int orientation) { + if (null == descriptors) { + throw new IllegalArgumentException("Image descriptor array for combined icon must not be null"); + } + if (descriptors.length == 0) { + throw new IllegalArgumentException("Amount of given image descriptors for combined icon must be at least 1."); + } + this.descriptors = Arrays.copyOf(descriptors, descriptors.length); + this.isVertical = orientation == SWT.VERTICAL; + int width = 0; + int height = 0; + for (ImageDescriptor imageDescriptor : descriptors) { + ImageData imageData = imageDescriptor.getImageData(); + if (isVertical) { + width = Math.max(width, imageData.width); + height += imageData.height; + } else { + width += imageData.width; + height = Math.max(height, imageData.height); + } + } + this.size = new Point(width, height); + + } + + /** + * {@inheritDoc} + */ + @Override + protected void drawCompositeImage(int width, int height) { + int xOffset = 0; + int yOffset = 0; + + for (ImageDescriptor imageDescriptor : descriptors) { + ImageData data = imageDescriptor.getImageData(); + if (isVertical) { + drawImage(data, 0, yOffset); + yOffset += data.height; + } else { + drawImage(data, xOffset, 0); + xOffset += data.width; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + protected Point getSize() { + return size; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/statushandlers/CustomStatusHandler.java b/inspectIT/src/info/novatec/inspectit/rcp/statushandlers/CustomStatusHandler.java new file mode 100644 index 000000000..af7803c5c --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/statushandlers/CustomStatusHandler.java @@ -0,0 +1,29 @@ +package info.novatec.inspectit.rcp.statushandlers; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.ui.statushandlers.StatusAdapter; +import org.eclipse.ui.statushandlers.StatusManager; +import org.eclipse.ui.statushandlers.WorkbenchErrorHandler; + +/** + * Custom status manager for displaying statuses and exceptions correctly. + * + * @author Ivan Senic + * + */ +public class CustomStatusHandler extends WorkbenchErrorHandler { + + /** + * {@inheritDoc} + */ + @Override + public void handle(StatusAdapter statusAdapter, int style) { + // if style is only log and we have an error + // then we want to show it as well to the user + if (StatusManager.LOG == style && statusAdapter.getStatus().getSeverity() == IStatus.ERROR) { + style |= StatusManager.SHOW; + } + + super.handle(statusAdapter, style); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/storage/InspectITStorageManager.java b/inspectIT/src/info/novatec/inspectit/rcp/storage/InspectITStorageManager.java new file mode 100644 index 000000000..5657f82a3 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/storage/InspectITStorageManager.java @@ -0,0 +1,1066 @@ +package info.novatec.inspectit.rcp.storage; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.indexing.storage.IStorageTreeComponent; +import info.novatec.inspectit.indexing.storage.impl.ArrayBasedStorageLeaf; +import info.novatec.inspectit.indexing.storage.impl.CombinedStorageBranch; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.repository.CmrRepositoryChangeListener; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.repository.StorageRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.StorageRepositoryDefinitionProvider; +import info.novatec.inspectit.rcp.storage.listener.StorageChangeListener; +import info.novatec.inspectit.rcp.storage.util.DataRetriever; +import info.novatec.inspectit.rcp.storage.util.DataUploader; +import info.novatec.inspectit.storage.IStorageData; +import info.novatec.inspectit.storage.LocalStorageData; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.StorageFileType; +import info.novatec.inspectit.storage.StorageManager; +import info.novatec.inspectit.storage.label.StringStorageLabel; +import info.novatec.inspectit.storage.label.type.impl.ExploredByLabelType; +import info.novatec.inspectit.storage.serializer.ISerializer; +import info.novatec.inspectit.storage.serializer.SerializationException; +import info.novatec.inspectit.util.ObjectUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.apache.commons.lang.mutable.MutableObject; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.swt.widgets.Display; + +import com.esotericsoftware.kryo.io.Input; + +/** + * {@link StorageManager} for GUI. + * + * @author Ivan Senic + * + */ +public class InspectITStorageManager extends StorageManager implements CmrRepositoryChangeListener { // NOPMD + + /** + * List of downloaded storages. + */ + private Set downloadedStorages = Collections.newSetFromMap(new ConcurrentHashMap(16, 0.75f, 2)); + + /** + * Map of mounted and online not available storages. + */ + private Set mountedNotAvailableStorages = Collections.newSetFromMap(new ConcurrentHashMap(16, 0.75f, 2)); + /** + * Map of mounted and online not available storages. + */ + private Map mountedAvailableStorages = new ConcurrentHashMap(16, 0.75f, 2); + + /** + * Cashed statuses of CMR repository definitions. + */ + private ConcurrentHashMap cachedRepositoriesStatus = new ConcurrentHashMap(16, 0.75f, 2); + + /** + * List of {@link StorageChangeListener}s. + */ + private List storageChangeListeners = new ArrayList(); + + /** + * {@link DataRetriever}. + */ + private DataRetriever dataRetriever; + + /** + * {@link DataUploader}. + */ + private DataUploader dataUploader; + + /** + * {@link StorageRepositoryDefinitionProvider}. + */ + private StorageRepositoryDefinitionProvider storageRepositoryDefinitionProvider; + + /** + * Mounts a new storage locally. Same as calling + * {@link #mountStorage(StorageData, CmrRepositoryDefinition, false, false)}. + * + * @param storageData + * Storage to mount. + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + * @param subMonitor + * {@link SubMonitor} to report to. + * @throws StorageException + * If storage directory can not be created or storage file can not be saved. + * @throws IOException + * If {@link IOException} occurs. + * @throws SerializationException + * If {@link SerializationException} occurs. + */ + public void mountStorage(StorageData storageData, CmrRepositoryDefinition cmrRepositoryDefinition, SubMonitor subMonitor) throws StorageException, IOException, SerializationException { + this.mountStorage(storageData, cmrRepositoryDefinition, false, false, subMonitor); + } + + /** + * Mounts a new storage locally. Provides option to specify if the complete download should be + * performed. + * + * @param storageData + * Storage to mount. + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + * @param fullyDownload + * Should storage be immediately fully down-loaded. Intended for future use. + * @param compressBefore + * If the fullyDownalod is true, this parameter can define if data files + * should be compressed on the fly before sent. + * @param subMonitor + * {@link SubMonitor} to report to. + * @throws StorageException + * If storage directory can not be created or storage file can not be saved. + * @throws IOException + * If {@link IOException} occurs. + * @throws SerializationException + * If {@link SerializationException} occurs. + * + */ + private void mountStorage(StorageData storageData, CmrRepositoryDefinition cmrRepositoryDefinition, boolean fullyDownload, boolean compressBefore, SubMonitor subMonitor) throws StorageException, + IOException, SerializationException { + LocalStorageData localStorageData = new LocalStorageData(storageData); + + Path directory = getStoragePath(localStorageData); + if (!Files.exists(directory)) { + try { + Files.createDirectories(directory); + } catch (IOException e) { + throw new StorageException("Could not create local storage directory.", e); + } + } + + if (fullyDownload) { + try { + subMonitor.setTaskName("Downloading storage files for storage '" + storageData.getName() + "'.."); + dataRetriever.downloadAndSaveStorageFiles(cmrRepositoryDefinition, storageData, directory, compressBefore, true, subMonitor, StorageFileType.values()); + downloadedStorages.add(localStorageData); + localStorageData.setFullyDownloaded(true); + } catch (Exception e) { + deleteLocalStorageData(localStorageData, false); + throw e; + } + } else { + try { + subMonitor.setTaskName("Downloading agent and indexing files for storage '" + storageData.getName() + "'.."); + dataRetriever.downloadAndSaveStorageFiles(cmrRepositoryDefinition, storageData, directory, compressBefore, true, subMonitor, StorageFileType.AGENT_FILE, StorageFileType.INDEX_FILE); + } catch (Exception e) { + deleteLocalStorageData(localStorageData, false); + throw e; + } + } + + writeLocalStorageDataToDisk(localStorageData); + + final String systemUserName = getSystemUsername(); + try { + if (null != systemUserName) { + StringStorageLabel mountedByLabel = new StringStorageLabel(systemUserName, new ExploredByLabelType()); + cmrRepositoryDefinition.getStorageService().addLabelToStorage(storageData, mountedByLabel, true); + } + } catch (final Exception e) { + Display.getDefault().syncExec(new Runnable() { + @Override + public void run() { + InspectIT.getDefault().createInfoDialog("'Mounted by' label with value '" + systemUserName + "'was not added to the storage. Exception message is:\n\n" + e.getMessage(), -1); + } + }); + } + + mountedAvailableStorages.put(localStorageData, cmrRepositoryDefinition); + } + + /** + * Returns if the storage data is already downloaded. + * + * @param storageData + * {@link StorageData}. + * @return Returns if the storage data is already downloaded. + */ + public boolean isFullyDownloaded(StorageData storageData) { + for (LocalStorageData lsd : downloadedStorages) { + if (ObjectUtils.equals(lsd.getId(), storageData.getId())) { + return lsd.isFullyDownloaded(); + } + } + + return false; + } + + /** + * Fully downloads selected storage. + * + * @param storageData + * StorageData. + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + * @param compressBefore + * Should data files be compressed on the fly before sent. + * @param subMonitor + * {@link SubMonitor} to report to. + * @throws Exception + * If any exception occurs. + */ + public void fullyDownloadStorage(StorageData storageData, CmrRepositoryDefinition cmrRepositoryDefinition, boolean compressBefore, SubMonitor subMonitor) throws Exception { + LocalStorageData localStorageData = null; + for (LocalStorageData lsd : mountedAvailableStorages.keySet()) { + if (ObjectUtils.equals(lsd.getId(), storageData.getId())) { + localStorageData = lsd; + break; + } + } + + if (null == localStorageData) { + mountStorage(storageData, cmrRepositoryDefinition, true, compressBefore, subMonitor); + return; + } + + if (localStorageData.isFullyDownloaded()) { + throw new StorageException("Storage is already fully downloaded."); + } + + Path directory = getStoragePath(localStorageData); + subMonitor.setTaskName("Downloading storage data files for storage '" + storageData.getName() + "'.."); + dataRetriever.downloadAndSaveStorageFiles(cmrRepositoryDefinition, storageData, directory, compressBefore, true, subMonitor, StorageFileType.DATA_FILE, StorageFileType.CACHED_DATA_FILE); + downloadedStorages.add(localStorageData); + localStorageData.setFullyDownloaded(true); + try { + writeLocalStorageDataToDisk(localStorageData); + } catch (Exception e) { + throw new StorageException("Could not save local storage information to disk.", e); + } + + } + + /** + * Deletes all local data saved for given {@link LocalStorageData}, unmount storage. + * + * @param localStorageData + * {@link LocalStorageData}. + * @throws IOException + * If deleting of the local data fails. + * @throws StorageException + * If serialization fails. + */ + public void deleteLocalStorageData(LocalStorageData localStorageData) throws IOException, StorageException { + this.deleteLocalStorageData(localStorageData, true); + } + + /** + * Deletes all local data saved for given {@link LocalStorageData}, unmount storage. + * + * @param localStorageData + * {@link LocalStorageData}. + * @param informListeners + * Should the listeners be informed. + * @throws IOException + * If deleting of the local data fails. + * @throws StorageException + * If serialization fails. + */ + private void deleteLocalStorageData(LocalStorageData localStorageData, boolean informListeners) throws IOException, StorageException { + localStorageData.setFullyDownloaded(false); + downloadedStorages.remove(localStorageData); + if (mountedAvailableStorages.containsKey(localStorageData) || mountedNotAvailableStorages.contains(localStorageData)) { + super.deleteStorageDataFromDisk(localStorageData, StorageFileType.DATA_FILE); + try { + writeLocalStorageDataToDisk(localStorageData); + } catch (SerializationException e) { + throw new StorageException(e); + } + } else { + super.deleteCompleteStorageDataFromDisk(localStorageData); + } + + if (informListeners) { + synchronized (storageChangeListeners) { + for (StorageChangeListener storageChangeListener : storageChangeListeners) { + storageChangeListener.storageLocallyDeleted(localStorageData); + } + } + } + } + + /** + * Informs the {@link InspectITStorageManager} that a {@link StorageData} has been remotely + * deleted. + * + * @param storageData + * {@link StorageData}. + * @throws Exception + * If deleting of the local data fails. + */ + public void storageRemotelyDeleted(StorageData storageData) throws Exception { + LocalStorageData localStorageData = null; + for (Map.Entry entry : mountedAvailableStorages.entrySet()) { + if (ObjectUtils.equals(entry.getKey().getId(), storageData.getId())) { + localStorageData = entry.getKey(); + break; + } + } + if (null != localStorageData && !localStorageData.isFullyDownloaded()) { + deleteLocalStorageData(localStorageData, false); + } else { + for (LocalStorageData notAvailable : mountedNotAvailableStorages) { + if (ObjectUtils.equals(notAvailable.getId(), storageData.getId())) { + localStorageData = notAvailable; + break; + } + } + if (null != localStorageData && !localStorageData.isFullyDownloaded()) { + deleteLocalStorageData(localStorageData, false); + } + } + + synchronized (storageChangeListeners) { + for (StorageChangeListener storageChangeListener : storageChangeListeners) { + storageChangeListener.storageRemotelyDeleted(storageData); + } + } + } + + /** + * Informs the Storage Manager that the {@link StorageData} has been remotely updated, so that + * existing local clone of the data can be updated. + * + * @param storageData + * {@link StorageData} that was updated. + * @throws IOException + * If {@link IOException} occurs during saving data to disk. + * @throws SerializationException + * If serialization fails during saving data to disk. + */ + public void storageRemotelyUpdated(StorageData storageData) throws IOException, SerializationException { + LocalStorageData localStorageData = getLocalDataForStorage(storageData); + if (null != localStorageData) { + updateLocalStorageData(localStorageData, storageData); + } + + synchronized (storageChangeListeners) { + for (StorageChangeListener storageChangeListener : storageChangeListeners) { + storageChangeListener.storageDataUpdated(storageData); + } + } + } + + /** + * Returns mounted and available storages, thus the ones that can be read from. + * + * @return List of {@link LocalStorageData}. + */ + public Collection getMountedAvailableStorages() { + return Collections.unmodifiableSet(mountedAvailableStorages.keySet()); + } + + /** + * Returns mounted but not available storages, thus the ones that can not be read from because + * there is no CMR that can handle them. + * + * @return List of {@link LocalStorageData}. + */ + public Collection getMountedUnavailableStorages() { + return Collections.unmodifiableSet(mountedNotAvailableStorages); + } + + /** + * Returns collection of fully downloaded storages. + * + * @return List of {@link LocalStorageData}. + */ + public Collection getDownloadedStorages() { + return Collections.unmodifiableSet(downloadedStorages); + } + + /** + * Returns storage {@link Path}. + * + * @param storageData + * Storage. + * @return Returns storage {@link Path}. + * @see Paths#get(String, String...) + */ + public Path getStoragePath(IStorageData storageData) { + return getDefaultStorageDirPath().resolve(storageData.getStorageFolder()); + } + + /** + * Loads initial local mounted storage information. + */ + public void startUp() { + List mountedStorages; + try { + mountedStorages = getMountedStoragesFromDisk(); + } catch (Exception e) { + mountedStorages = Collections.emptyList(); + } + Map onlineStorages = getOnlineStorages(); + + for (LocalStorageData localStorageData : mountedStorages) { + if (localStorageData.isFullyDownloaded()) { + downloadedStorages.add(localStorageData); + } + boolean availableOnline = false; + for (Map.Entry entry : onlineStorages.entrySet()) { + if (ObjectUtils.equals(entry.getKey().getId(), localStorageData.getId())) { + availableOnline = true; + try { + updateLocalStorageData(localStorageData, entry.getKey()); + } catch (final Exception e) { + final String name = localStorageData.getName(); + Display.getDefault().syncExec(new Runnable() { + @Override + public void run() { + InspectIT.getDefault().createErrorDialog("Local data for the storage '" + name + "' could not be updated with the online data.", e, -1); + } + }); + } + mountedAvailableStorages.put(localStorageData, entry.getValue()); + break; + } + } + if (!availableOnline) { + mountedNotAvailableStorages.add(localStorageData); + } + } + + InspectIT.getDefault().getCmrRepositoryManager().addCmrRepositoryChangeListener(this); + } + + /** + * Instantiates the new storage repository definition based on the {@link LocalStorageData} + * provided. Note that the local storage data has to be in the collection of + * {@link #getMountedAvailableStorages()}. Otherwise the creation will fail with an Exception + * being thrown.. + *

+ * A special care needs to be taken with the method, because the the returned object could be + * quite big, since it will hold the complete indexing tree. Thus, it is important that the + * caller of this method take responsibility to make earlier created definitions ready for + * garbage collection as soon as they are not needed anymore. + * + * @param localStorageData + * {@link LocalStorageData} to create the definition for. + * @return {@link StorageRepositoryDefinition}. + * @throws StorageException + * If the wanted {@link LocalStorageData} is not available. + * @throws SerializationException + * If {@link SerializationException} occurs. + * @throws IOException + * If {@link IOException} occurs. + * + */ + public StorageRepositoryDefinition getStorageRepositoryDefinition(LocalStorageData localStorageData) throws StorageException, SerializationException, IOException { + // check if it is available + if (!mountedAvailableStorages.keySet().contains(localStorageData) && !downloadedStorages.contains(localStorageData)) { + throw new StorageException("The storage is not fully downloaded, and it's repository could not be found. The Storage repository definition could not be created."); + } + + // find CMR repository def, allow null for downloaded and not mounted storages + CmrRepositoryDefinition cmrRepositoryDefinition = null; + cmrRepositoryDefinition = mountedAvailableStorages.get(localStorageData); + + // get agents + List platformIdents = getPlatformIdentsLocally(localStorageData); + if (null == platformIdents) { + platformIdents = Collections.emptyList(); + } + + // get indexing tree + IStorageTreeComponent indexingTree = getIndexingTree(localStorageData); + if (null == indexingTree) { + indexingTree = new ArrayBasedStorageLeaf(); + } + + // create new storage repository definition + StorageRepositoryDefinition storageRepositoryDefinition = storageRepositoryDefinitionProvider.createStorageRepositoryDefinition(); + storageRepositoryDefinition.setAgents(platformIdents); + storageRepositoryDefinition.setIndexingTree(indexingTree); + storageRepositoryDefinition.setCmrRepositoryDefinition(cmrRepositoryDefinition); + storageRepositoryDefinition.setLocalStorageData(localStorageData); + storageRepositoryDefinition.initServices(); + return storageRepositoryDefinition; + } + + /** + * Checks if the storage is locally mounted. + * + * @param storageData + * Storage data to check. + * @return True if storage is mounted, false otherwise. + */ + public boolean isStorageMounted(StorageData storageData) { + return getLocalDataForStorage(storageData) != null; + } + + /** + * Returns the local data for storage if the storage is mounted or downloaded. + * + * @param storageData + * Storage data to check. + * @return {@link LocalStorageData}. + */ + public LocalStorageData getLocalDataForStorage(StorageData storageData) { + for (LocalStorageData downloadedStorage : downloadedStorages) { + if (ObjectUtils.equals(downloadedStorage.getId(), storageData.getId())) { + return downloadedStorage; + } + } + for (LocalStorageData mountedStorage : mountedNotAvailableStorages) { + if (ObjectUtils.equals(storageData.getId(), mountedStorage.getId())) { + return mountedStorage; + } + } + for (LocalStorageData mountedStorage : mountedAvailableStorages.keySet()) { + if (ObjectUtils.equals(storageData.getId(), mountedStorage.getId())) { + return mountedStorage; + } + } + return null; + } + + /** + * Registers a {@link StorageChangeListener} if the same listener does not already exist. + * + * @param storageChangeListener + * {@link StorageChangeListener} to add. + */ + public void addStorageChangeListener(StorageChangeListener storageChangeListener) { + synchronized (storageChangeListeners) { + if (!storageChangeListeners.contains(storageChangeListener)) { + storageChangeListeners.add(storageChangeListener); + } + } + } + + /** + * Removes a {@link StorageChangeListener}. + * + * @param storageChangeListener + * {@link StorageChangeListener} to remove. + */ + public void removeStorageChangeListener(StorageChangeListener storageChangeListener) { + synchronized (storageChangeListeners) { + storageChangeListeners.remove(storageChangeListener); + } + } + + /** + * Uploads a file to the {@link CmrRepositoryDefinition} storage uploads. + * + * @param fileName + * Name of file. + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + * @param subMonitor + * {@link SubMonitor} to report progress to. + * @throws Exception + * If upload file does not exist or upload fails. + */ + public void uploadZippedStorage(String fileName, CmrRepositoryDefinition cmrRepositoryDefinition, SubMonitor subMonitor) throws Exception { + Path file = Paths.get(fileName); + Path relativizePath = file.getParent(); + String tmpDir = "tmp" + UUID.randomUUID().hashCode(); + subMonitor.setTaskName("Uploading storage file.."); + // no compressing since it is already zipped + dataUploader.uploadFileToStorageUploads(file, relativizePath, tmpDir, cmrRepositoryDefinition, subMonitor); + } + + /** + * Uploads a complete storage to the {@link CmrRepositoryDefinition} upload folder. All files + * belonging to the local storage data will be uploaded to the temporary directory. + * + * @param localStorageData + * Storage to upload. + * @param cmrRepositoryDefinition + * Repository definition. + * @param subMonitor + * The monitor to report upload progress to. + * @throws Exception + * If storage is not fully downloaded or exception occurs during upload. + */ + public void uploadCompleteStorage(LocalStorageData localStorageData, final CmrRepositoryDefinition cmrRepositoryDefinition, SubMonitor subMonitor) throws Exception { + if (!localStorageData.isFullyDownloaded()) { + throw new StorageException("Can not upload storage that is not fully downloaded."); + } + + String tmpDir = "tmp" + UUID.randomUUID().hashCode(); + Path storageDir = getStoragePath(localStorageData); + final List toUpload = new ArrayList(); + Files.walkFileTree(storageDir, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + toUpload.add(file); + return FileVisitResult.CONTINUE; + } + }); + subMonitor.setTaskName("Uploading storage files.."); + dataUploader.uploadFileToStorageUploads(toUpload, storageDir, tmpDir, cmrRepositoryDefinition, subMonitor); + } + + /** + * Compresses the content of the local storage data folder to the file. File name is provided + * via given path. If the file already exists, it will be deleted first. + * + * @param localStorageData + * {@link LocalStorageData} to zip. + * @param zipFileName + * Zip file name. + * @throws StorageException + * If the storage is not fully downloaded. + * @throws IOException + * If {@link IOException} occurs during compressing. + */ + public void zipStorageData(LocalStorageData localStorageData, String zipFileName) throws StorageException, IOException { + if (!localStorageData.isFullyDownloaded()) { + throw new StorageException("Local storage data is not fully downloaded."); + } else { + Path zipPath = Paths.get(zipFileName); + if (Files.exists(zipPath)) { + Files.delete(zipPath); + } + + try { + super.zipStorageData(localStorageData, zipPath); + } catch (IOException e) { + Files.deleteIfExists(zipPath); + throw e; + } + } + } + + /** + * Zips the remote storage files to the file. File name is provided via given path. If the file + * already exists, it will be deleted first. + * + * @param storageData + * Remote storage to zip. + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} where storage is located. + * @param zipFileName + * Zip file name. + * @param compressBefore + * Defines if the data should be compressed before downloading. + * @param subMonitor + * {@link SubMonitor} to report to. + * @throws StorageException + * If serialization of data fails during zipping. + * @throws IOException + * If {@link IOException} occurs during compressing. + */ + public void zipStorageData(StorageData storageData, CmrRepositoryDefinition cmrRepositoryDefinition, String zipFileName, boolean compressBefore, SubMonitor subMonitor) throws StorageException, + IOException { + Path zipPath = Paths.get(zipFileName); + if (Files.exists(zipPath)) { + Files.delete(zipPath); + } + + try (final ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zipPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE))) { + // download and pack at the same time + subMonitor.setTaskName("Downloading and packing storage files.."); + dataRetriever.downloadAndZipStorageFiles(cmrRepositoryDefinition, storageData, zos, compressBefore, false, subMonitor, StorageFileType.values()); + + // add local storage data info + LocalStorageData localStorageData = new LocalStorageData(storageData); + localStorageData.setFullyDownloaded(true); + String fileName = localStorageData.getId() + StorageFileType.LOCAL_STORAGE_FILE.getExtension(); + ZipEntry zipEntry = new ZipEntry(fileName); + zos.putNextEntry(zipEntry); + serializeDataToOutputStream(localStorageData, zos, false); + zos.closeEntry(); + } catch (IOException | StorageException e) { + Files.deleteIfExists(zipPath); + throw e; + } catch (SerializationException e) { + Files.deleteIfExists(zipPath); + throw new StorageException("Could not write local storage data to packed storage file.", e); + } + + } + + /** + * Returns the {@link StorageData} object that exists in the compressed storage file. + * + * @param zipFileName + * Compressed storage file name. + * @return {@link IStorageData} object or null if the given file is not of correct + * type or does not exist. + */ + public IStorageData getStorageDataFromZip(String zipFileName) { + return this.getStorageDataFromZip(Paths.get(zipFileName)); + } + + /** + * Unzips the content of the zip file provided to the default storage folder. The method will + * first unzip the complete content of the zip file to the temporary folder and then rename the + * temporary folder to match the storage ID. + *

+ * The method will also check if the imported storage is available online, and if it is will + * update the local data saved. + * + * @param fileName + * File to unzip. + * @throws StorageException + * If given file does not exist or content is not proper. + * @throws IOException + * If {@link IOException} occurs. + * @throws SerializationException + * If serialization exception occurs if data needs to be updated. + * + */ + public void unzipStorageData(String fileName) throws StorageException, IOException, SerializationException { + Path zipPath = Paths.get(fileName); + IStorageData packedStorageData = getStorageDataFromZip(zipPath); + this.unzipStorageData(zipPath, getStoragePath(packedStorageData)); + + List localStorageDataList = getMountedStoragesFromDisk(); + for (LocalStorageData localStorageData : localStorageDataList) { + if (localStorageData.isFullyDownloaded() && !downloadedStorages.contains(localStorageData)) { + downloadedStorages.add(localStorageData); + for (StorageData storageData : getOnlineStorages().keySet()) { + if (ObjectUtils.equals(storageData.getId(), localStorageData.getId())) { + updateLocalStorageData(localStorageData, storageData); + break; + } + } + break; + } + } + } + + /** + * {@inheritDoc} + */ + public void repositoryAdded(CmrRepositoryDefinition cmrRepositoryDefinition) { + this.addMountedStorages(cmrRepositoryDefinition); + } + + /** + * {@inheritDoc} + */ + public void repositoryRemoved(CmrRepositoryDefinition cmrRepositoryDefinition) { + this.removeMountedStorages(cmrRepositoryDefinition); + } + + /** + * {@inheritDoc} + */ + public void repositoryDataUpdated(CmrRepositoryDefinition cmrRepositoryDefinition) { + } + + /** + * {@inheritDoc} + */ + public void repositoryAgentDeleted(CmrRepositoryDefinition cmrRepositoryDefinition, PlatformIdent agent) { + } + + /** + * {@inheritDoc} + */ + public void repositoryOnlineStatusUpdated(CmrRepositoryDefinition cmrRepositoryDefinition, OnlineStatus oldStatus, OnlineStatus newStatus) { + if (newStatus != OnlineStatus.CHECKING) { + OnlineStatus cachedStatus = cachedRepositoriesStatus.get(cmrRepositoryDefinition); + if (!ObjectUtils.equals(cachedStatus, newStatus)) { + if (newStatus == OnlineStatus.ONLINE) { + this.addMountedStorages(cmrRepositoryDefinition); + } else if (newStatus == OnlineStatus.OFFLINE) { + this.removeMountedStorages(cmrRepositoryDefinition); + } + } + cachedRepositoriesStatus.put(cmrRepositoryDefinition, newStatus); + } + } + + /** + * Updates the information of the local storage data saved on the client machine with the data + * provided in the storage data available online. + * + * @param localStorageData + * Local storage data to update. + * @param storageData + * Storage data that holds new information. + * @throws SerializationException + * If serialization of data fails. + * @throws IOException + * If {@link IOException} occurs during data saving to disk. + */ + private void updateLocalStorageData(LocalStorageData localStorageData, StorageData storageData) throws IOException, SerializationException { + localStorageData.copyStorageDataInformation(storageData); + writeLocalStorageDataToDisk(localStorageData); + } + + /** + * Adds mounted storages that are bounded to the given {@link CmrRepositoryDefinition} to + * "available" map. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + */ + private void addMountedStorages(CmrRepositoryDefinition cmrRepositoryDefinition) { + if (cmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.ONLINE) { + List closedStorages = cmrRepositoryDefinition.getStorageService().getReadableStorages(); + List newAvailableStoarges = new ArrayList(); + for (LocalStorageData localStorageData : mountedNotAvailableStorages) { + for (StorageData storageData : closedStorages) { + if (ObjectUtils.equals(localStorageData.getId(), storageData.getId())) { + newAvailableStoarges.add(localStorageData); + try { + updateLocalStorageData(localStorageData, storageData); + } catch (final Exception e) { + final String name = localStorageData.getName(); + Display.getDefault().syncExec(new Runnable() { + @Override + public void run() { + InspectIT.getDefault().createErrorDialog("Local data for the storage '" + name + "' could not be updated with the online data.", e, -1); + } + }); + } + mountedAvailableStorages.put(localStorageData, cmrRepositoryDefinition); + break; + } + } + } + + if (!newAvailableStoarges.isEmpty()) { + mountedNotAvailableStorages.removeAll(newAvailableStoarges); + } + } + } + + /** + * Removes mounted storages that are bounded to the given {@link CmrRepositoryDefinition} to + * "available" map. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + */ + private void removeMountedStorages(CmrRepositoryDefinition cmrRepositoryDefinition) { + List removeList = new ArrayList(); + for (Map.Entry entry : mountedAvailableStorages.entrySet()) { + if (!entry.getKey().isFullyDownloaded()) { + if (ObjectUtils.equals(entry.getValue(), cmrRepositoryDefinition)) { + removeList.add(entry.getKey()); + } + } + } + if (!removeList.isEmpty()) { + mountedAvailableStorages.keySet().removeAll(removeList); + mountedNotAvailableStorages.addAll(removeList); + } + } + + /** + * Loads {@link PlatformIdent}s from a disk for a storage. + * + * @param storageData + * {@link IStorageData} + * @return List of {@link PlatformIdent}s involved in the storage data or null if no file + * exists. + * @throws IOException + * If {@link IOException} occurs. + * @throws SerializationException + * If data can not be deserialized. + */ + private List getPlatformIdentsLocally(final IStorageData storageData) throws IOException, SerializationException { + Path storagePath = getStoragePath(storageData); + List returnList = this.getObjectsByFileTreeWalk(storagePath, StorageFileType.AGENT_FILE.getExtension()); + if (!returnList.isEmpty()) { + return returnList; + } else { + return null; + } + } + + /** + * Loads indexing tree from a disk for a storage. + * + * @param storageData + * {@link IStorageData} + * @return Indexing tree or null if it can not be found. + * @throws IOException + * If {@link IOException} occurs. + * @throws SerializationException + * If data can not be deserialized. + */ + private IStorageTreeComponent getIndexingTree(final IStorageData storageData) throws IOException, SerializationException { + Path storagePath = getStoragePath(storageData); + List> indexingTrees = this.getObjectsByFileTreeWalk(storagePath, StorageFileType.INDEX_FILE.getExtension()); + if (!indexingTrees.isEmpty()) { + if (indexingTrees.size() == 1) { + return indexingTrees.get(0); + } else { + CombinedStorageBranch combinedStorageBranch = new CombinedStorageBranch(indexingTrees); + return combinedStorageBranch; + } + } else { + return null; + } + } + + /** + * Returns all storages that have been mounted locally. + * + * @return Returns all storages that have been mounted locally as a list of + * {@link LocalStorageData}. + * @throws IOException + * If {@link IOException} occurs. + * @throws SerializationException + * If data can not be deserialized. + */ + private List getMountedStoragesFromDisk() throws IOException, SerializationException { + return this.getObjectsByFileTreeWalk(getDefaultStorageDirPath(), StorageFileType.LOCAL_STORAGE_FILE.getExtension()); + } + + /** + * Reads the objects from files that are in a given path or sub-paths. Note that generic can be + * used to specify the wanted class. How ever, if the object loaded from a file is not of a + * wanted class, {@link ClassCastException} will be thrown as usual. + * + * @param + * Wanted type. Use object if it is uncertain what types object will be. + * @param path + * {@link Path} to look in. + * @param fileSufix + * Ending of the files (extension). + * @return List of deserialized objects. + * @throws IOException + * If {@link IOException} occurs. + * @throws SerializationException + * If data can not be deserialized. + * + */ + private List getObjectsByFileTreeWalk(Path path, final String fileSufix) throws IOException, SerializationException { + if (!Files.isDirectory(path)) { + return Collections.emptyList(); + } + + final ISerializer serializer = getSerializationManagerProvider().createSerializer(); + final MutableObject mutableException = new MutableObject(); + final List returnList = new ArrayList(); + Files.walkFileTree(path, new SimpleFileVisitor() { + + @SuppressWarnings("unchecked") + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.toString().endsWith(fileSufix)) { + InputStream inputStream = null; + Input input = null; + try { + inputStream = Files.newInputStream(file, StandardOpenOption.READ); + input = new Input(inputStream); + Object deserialized = serializer.deserialize(input); + returnList.add((E) deserialized); + } catch (SerializationException e) { + mutableException.setValue(e); + return FileVisitResult.TERMINATE; + } finally { + if (null != input) { + input.close(); + } + } + + } + return FileVisitResult.CONTINUE; + } + }); + + SerializationException serializationException = (SerializationException) mutableException.getValue(); + if (null != serializationException) { + throw serializationException; + } else { + return returnList; + } + } + + /** + * Returns map of online available storages with their {@link CmrRepositoryDefinition} as a + * value. + * + * @return Map of online available storages with their {@link CmrRepositoryDefinition} as a + * value. + */ + private Map getOnlineStorages() { + Map storageMap = new HashMap(); + List allRepositories = InspectIT.getDefault().getCmrRepositoryManager().getCmrRepositoryDefinitions(); + for (CmrRepositoryDefinition cmrRepositoryDefinition : allRepositories) { + if (cmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.ONLINE) { + List closedStorages = cmrRepositoryDefinition.getStorageService().getReadableStorages(); + if (null != closedStorages) { + for (StorageData storageData : closedStorages) { + storageMap.put(storageData, cmrRepositoryDefinition); + } + } + } + } + return storageMap; + } + + /** + * {@inheritDoc} + */ + protected Path getDefaultStorageDirPath() { + return InspectIT.getDefault().getRuntimeDir().resolve(getStorageDefaultFolder()).toAbsolutePath(); + } + + /** + * + * @return Returns the system username. + */ + private String getSystemUsername() { + return System.getProperty("user.name"); + } + + /** + * @param dataRetriever + * the httpDataRetriever to set + */ + public void setDataRetriever(DataRetriever dataRetriever) { + this.dataRetriever = dataRetriever; + } + + /** + * Sets {@link #dataUploader}. + * + * @param dataUploader + * New value for {@link #dataUploader} + */ + public void setDataUploader(DataUploader dataUploader) { + this.dataUploader = dataUploader; + } + + /** + * Sets {@link #storageRepositoryDefinitionProvider}. + * + * @param storageRepositoryDefinitionProvider + * New value for {@link #storageRepositoryDefinitionProvider} + */ + public void setStorageRepositoryDefinitionProvider(StorageRepositoryDefinitionProvider storageRepositoryDefinitionProvider) { + this.storageRepositoryDefinitionProvider = storageRepositoryDefinitionProvider; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/storage/http/TransferDataMonitor.java b/inspectIT/src/info/novatec/inspectit/rcp/storage/http/TransferDataMonitor.java new file mode 100644 index 000000000..9f6cd3d05 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/storage/http/TransferDataMonitor.java @@ -0,0 +1,293 @@ +package info.novatec.inspectit.rcp.storage.http; + +import info.novatec.inspectit.rcp.formatter.NumberFormatter; + +import java.util.Map; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.ui.progress.UIJob; + +/** + * Transfer monitor class that collects {@link DataSample}s during the transfer and provides + * informations about the average transfer rate, total bytes transfered, etc. + * + * @author Ivan Senic + * + */ +public class TransferDataMonitor { + + /** + * Millis to pass to display the message. + */ + private static final long DISPLAY_MESSAGE_RATE = 1000; + + /** + * Percentage of file size that will be used if Gzip is used. + */ + private static final double GZIP_FILE_SIZE_RATIO = 0.15; + + /** + * This value is provided in the CMR by default and represents minimum file size for which GZIp + * compression will be used. + */ + private static final long MIN_GZIP_FILE_SIZE = 1048576; + + /** + * Amount of bytes transfered. + */ + private long totalBytesTransfered; + + /** + * Time when download started. + */ + private long downloadStartTime; + + /** + * Sub monitor to report to. + */ + private SubMonitor subMonitor; + + /** + * Files that have to be downloaded. + */ + private Map files; + + /** + * If the GZip compression is active. + */ + private boolean gzipCompression; + + /** + * Amount of finished transfers. + */ + private int filesCount = 0; + + /** + * Total size that needs to be downloaded. + */ + private long totalSize; + + /** + * Amount of size we believe in finished files. + */ + private long finishedFilesSize; + + /** + * Size of the currently downloaded file. + */ + private long currentFileSize; + + /** + * Amount of bytes that we reported for current file. + */ + private long currentFileReal; + + /** + * Job for displying the message. + */ + private DisplayMessageJob displayMessageJob = new DisplayMessageJob(); + + /** + * Default constructor. Same as calling + * {@link TransferDataMonitor#TransferDataMonitor(SubMonitor, Map, false)}. + * + * @param subMonitor + * Monitor to report to. + * @param files + * Files to be transfered. + */ + public TransferDataMonitor(SubMonitor subMonitor, Map files) { + this(subMonitor, files, false); + } + + /** + * @param subMonitor + * Monitor to report to. + * @param files + * Files to be transfered. + * @param gzipCompression + * If GZip compression will be active. + */ + public TransferDataMonitor(SubMonitor subMonitor, Map files, boolean gzipCompression) { + this.subMonitor = subMonitor; + this.files = files; + this.gzipCompression = gzipCompression; + + totalSize = 0; + // try to calculate the total size based on if the gzip is on + for (Map.Entry entry : files.entrySet()) { + totalSize += getFileSize(entry.getValue().longValue()); + } + + subMonitor.setWorkRemaining((int) totalSize); + displayMessageJob.schedule(); + } + + /** + * Marks the start of the file download. + * + * @param fileName + * Name of the file that is downloaded. + */ + public void startTransfer(String fileName) { + if (0 == filesCount) { + downloadStartTime = System.currentTimeMillis(); + } + + // reset values for the current file + currentFileReal = 0; + currentFileSize = getFileSize(files.get(fileName).longValue()); + } + + /** + * Informs that the download of the file has ended. + * + * @param fileName + * Name of the file that has been downloaded. + */ + public void endTransfer(String fileName) { + // if the file is smaller than expected (can happen with gzip) add remaining expected size + // to the submonitor + filesCount++; + long leftNotReported = currentFileSize - currentFileReal; + if (leftNotReported > 0) { + subMonitor.worked((int) leftNotReported); + } + + // update the total finised files size + long originalSize = files.get(fileName).longValue(); + finishedFilesSize += getFileSize(originalSize); + + if (files.size() == filesCount) { + // when last file is finished we cancel the message job and inform submonitor + displayMessageJob.cancel(); + subMonitor.subTask(""); + subMonitor.done(); + } + } + + /** + * Adds the sample. + * + * @param byteCount + * Bytes transfered. + */ + public void addSample(long byteCount) { + // transfer rate + totalBytesTransfered += byteCount; + + // reporting stuff + long reportSize = currentFileSize - currentFileReal; + // only report if there is some size left + if (reportSize > 0) { + reportSize = Math.min(reportSize, byteCount); + subMonitor.worked((int) reportSize); + } + currentFileReal += byteCount; + } + + /** + * Displays the current state on monitor. + */ + private void displayMessageOnMonitor() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("File "); + stringBuilder.append(filesCount + 1); + stringBuilder.append('/'); + stringBuilder.append(files.size()); + stringBuilder.append(" ("); + stringBuilder.append(NumberFormatter.humanReadableByteCount(totalBytesTransfered)); + if (!gzipCompression) { + stringBuilder.append(" out of "); + stringBuilder.append(NumberFormatter.humanReadableByteCount(totalSize)); + } + stringBuilder.append(" @ "); + stringBuilder.append(NumberFormatter.humanReadableByteCount((long) getAverageTransferRate())); + stringBuilder.append("/s) Remaining time: "); + if (gzipCompression) { + stringBuilder.append("app. "); + } + long quasiBytesLeft = totalSize - finishedFilesSize; + if (currentFileReal < currentFileSize) { + quasiBytesLeft -= currentFileReal; + } else { + quasiBytesLeft -= currentFileSize; + } + // always report at least one sec + long millisLeft = getMillisLeft(quasiBytesLeft); + millisLeft += millisLeft % 1000; + stringBuilder.append(NumberFormatter.humanReadableMillisCount(millisLeft, true)); + subMonitor.subTask(stringBuilder.toString()); + } + + /** + * Returns the average transfer rate. + * + * @return Returns the average transfer rate. + */ + private double getAverageTransferRate() { + return (double) totalBytesTransfered / ((double) (System.currentTimeMillis() - downloadStartTime) / 1000.0d); + } + + /** + * Returns time left for the given amounts of bytes to be downloaded. Note that this is not + * related to the current transfer rate, but to the time since the download started. Thus this + * can be used as the information when it is gonna be completely over. + * + * @param bytesMore + * Bytes left to be downloaded. + * @return Estimated time left in milliseconds. + */ + private long getMillisLeft(long bytesMore) { + return (long) ((bytesMore * 1000.0d) / getAverageTransferRate()); + } + + /** + * Returns the size of the file based on if the current download is compressed or not. + * + * @param originalFileSize + * Original file size. + * @return Returns file size to use. + */ + private long getFileSize(long originalFileSize) { + if (gzipCompression && originalFileSize > MIN_GZIP_FILE_SIZE) { + return (long) (originalFileSize * GZIP_FILE_SIZE_RATIO); + } else { + return originalFileSize; + } + } + + /** + * Job that will display the message. + * + * @author Ivan Senic + * + */ + private class DisplayMessageJob extends UIJob { + + /** + * Default constructor. + */ + public DisplayMessageJob() { + super("Display Message"); + setUser(false); + } + + /** + * {@inheritDoc} + */ + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + displayMessageOnMonitor(); + if (!monitor.isCanceled()) { + schedule(DISPLAY_MESSAGE_RATE); + } + return Status.OK_STATUS; + } + + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/storage/http/TransferRateInputStream.java b/inspectIT/src/info/novatec/inspectit/rcp/storage/http/TransferRateInputStream.java new file mode 100644 index 000000000..3e29082aa --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/storage/http/TransferRateInputStream.java @@ -0,0 +1,68 @@ +package info.novatec.inspectit.rcp.storage.http; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Special input stream that reports the bytes received to the {@link TransferDataMonitor}. Since it + * is extending the FilterInputStream all operations are forwarded to the wrapped stream. + * + * @author Ivan Senic + * + */ +public class TransferRateInputStream extends FilterInputStream { + + /** + * Data monitor to report to. + */ + private TransferDataMonitor transferDataMonitor; + + /** + * Default constructor. + * + * @param inputStream + * Stream. + * @param transferDataMonitor + * {@link TransferDataMonitor} to report to. + */ + public TransferRateInputStream(InputStream inputStream, TransferDataMonitor transferDataMonitor) { + super(inputStream); + this.transferDataMonitor = transferDataMonitor; + } + + /** + * {@inheritDoc} + */ + @Override + public int read() throws IOException { + int b = super.read(); + if (b >= 0) { + markReceived(b); + } + return b; + } + + /** + * {@inheritDoc} + */ + @Override + public int read(byte[] data, int off, int len) throws IOException { + int cnt = super.read(data, off, len); + if (cnt >= 0) { + markReceived(cnt); + } + return cnt; + } + + /** + * Marks a received amount of bytes. + * + * @param byteCount + * Byte count. + */ + private void markReceived(long byteCount) { + transferDataMonitor.addSample(byteCount); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/storage/http/TransferRateOutputStream.java b/inspectIT/src/info/novatec/inspectit/rcp/storage/http/TransferRateOutputStream.java new file mode 100644 index 000000000..eb65dcf46 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/storage/http/TransferRateOutputStream.java @@ -0,0 +1,53 @@ +package info.novatec.inspectit.rcp.storage.http; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Special output stream that reports the bytes sent to the {@link TransferDataMonitor}. Since it is + * extending the FilterOutputStream all operations are forwarded to the wrapped stream. + * + * @author Ivan Senic + * + */ +public class TransferRateOutputStream extends FilterOutputStream { + + /** + * Data monitor to report to. + */ + private TransferDataMonitor transferDataMonitor; + + /** + * Default constructor. + * + * @param outputStream + * Stream. + * @param transferDataMonitor + * Data monitor to report to. + */ + public TransferRateOutputStream(OutputStream outputStream, TransferDataMonitor transferDataMonitor) { + super(outputStream); + this.transferDataMonitor = transferDataMonitor; + } + + /** + * {@inheritDoc} + */ + @Override + public void write(byte[] b, int off, int len) throws IOException { + super.write(b, off, len); + markSent(len); + } + + /** + * Marks a sent amount of bytes. + * + * @param byteCount + * Byte count. + */ + private void markSent(long byteCount) { + transferDataMonitor.addSample(byteCount); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/AbstractStorageLabelComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/AbstractStorageLabelComposite.java new file mode 100644 index 000000000..83b6be50e --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/AbstractStorageLabelComposite.java @@ -0,0 +1,52 @@ +package info.novatec.inspectit.rcp.storage.label.composite; + +import info.novatec.inspectit.storage.label.AbstractStorageLabel; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Listener; + +/** + * Abstract class for all composite that are able to define a {@link AbstractStorageLabel}. + * + * @author Ivan Senic + * + */ +public abstract class AbstractStorageLabelComposite extends Composite { + + /** + * Default constructor. + * + * @param parent + * Parent. + * @param style + * Style. + * @see Composite#Composite(Composite, int) + */ + public AbstractStorageLabelComposite(Composite parent, int style) { + super(parent, style); + } + + /** + * Returns created {@link AbstractStorageLabel}. + * + * @return Returns created {@link AbstractStorageLabel}. + */ + public abstract AbstractStorageLabel getStorageLabel(); + + /** + * Returns if the input is valid. + * + * @return Returns if the input is valid. + */ + public abstract boolean isInputValid(); + + /** + * Adds the listener that sub-classes should register in the correct way to the widgets, based + * on the widgets used. + * + * @param pageCompletionListener + * Listener to register. + */ + public abstract void addListener(Listener pageCompletionListener); + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/impl/BooleanStorageLabelComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/impl/BooleanStorageLabelComposite.java new file mode 100644 index 000000000..6912e0e0c --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/impl/BooleanStorageLabelComposite.java @@ -0,0 +1,118 @@ +package info.novatec.inspectit.rcp.storage.label.composite.impl; + +import info.novatec.inspectit.rcp.storage.label.composite.AbstractStorageLabelComposite; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.label.BooleanStorageLabel; +import info.novatec.inspectit.storage.label.type.AbstractStorageLabelType; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; + +/** + * Composite for selecting the {@link BooleanStorageLabel}. + * + * @author Ivan Senic + * + */ +public class BooleanStorageLabelComposite extends AbstractStorageLabelComposite { + + /** + * Label type. + */ + private AbstractStorageLabelType booleanStorageLabelType; + + /** + * Button for selecting YES. + */ + private Button yesButton; + + /** + * Should a label be displayed next to the selection widget. + */ + private boolean showLabel; + + /** + * Default constructor. + * + * @param parent + * Parent. + * @param style + * Style. + * @param booleanStorageLabelType + * Storage label type. + * @see Composite#Composite(Composite, int) + */ + public BooleanStorageLabelComposite(Composite parent, int style, AbstractStorageLabelType booleanStorageLabelType) { + this(parent, style, booleanStorageLabelType, true); + } + + /** + * Secondary constructor. Defines if the label should be displayed in the composite. + * + * @param parent + * Parent. + * @param style + * Style. + * @param booleanStorageLabelType + * Storage label type. + * @param showLabel + * Should label be displayed next to the selection widget. + * @see Composite#Composite(Composite, int) + */ + public BooleanStorageLabelComposite(Composite parent, int style, AbstractStorageLabelType booleanStorageLabelType, boolean showLabel) { + super(parent, style); + this.booleanStorageLabelType = booleanStorageLabelType; + this.showLabel = showLabel; + initComposite(); + } + + /** + * Initializes the composite. + */ + private void initComposite() { + if (showLabel) { + GridLayout gl = new GridLayout(3, true); + this.setLayout(gl); + new Label(this, SWT.NONE).setText("Select value:"); + } else { + GridLayout gl = new GridLayout(2, true); + this.setLayout(gl); + } + + yesButton = new Button(this, SWT.RADIO); + yesButton.setText("Yes"); + yesButton.setSelection(true); + + Button noButton = new Button(this, SWT.RADIO); + noButton.setText("No"); + noButton.setSelection(false); + } + + /** + * {@inheritDoc} + */ + @Override + public AbstractStorageLabel getStorageLabel() { + return new BooleanStorageLabel(yesButton.getSelection(), booleanStorageLabelType); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isInputValid() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void addListener(Listener pageCompletionListener) { + yesButton.addListener(SWT.Selection, pageCompletionListener); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/impl/DateStorageLabelComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/impl/DateStorageLabelComposite.java new file mode 100644 index 000000000..684ab3445 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/impl/DateStorageLabelComposite.java @@ -0,0 +1,119 @@ +package info.novatec.inspectit.rcp.storage.label.composite.impl; + +import info.novatec.inspectit.rcp.storage.label.composite.AbstractStorageLabelComposite; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.label.DateStorageLabel; +import info.novatec.inspectit.storage.label.type.AbstractStorageLabelType; + +import java.util.Date; + +import org.eclipse.nebula.widgets.cdatetime.CDT; +import org.eclipse.nebula.widgets.cdatetime.CDateTime; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; + +/** + * Composite for selecting the {@link DateStorageLabel}. + * + * @author Ivan Senic + * + */ +public class DateStorageLabelComposite extends AbstractStorageLabelComposite { + + /** + * Label type. + */ + private AbstractStorageLabelType dateStorageLabelType; + + /** + * {@link CDateTime} for selecting Date. + */ + private CDateTime cDateTime; + + /** + * Should a label be displayed next to the selection widget. + */ + private boolean showLabel; + + /** + * Default constructor. + * + * @param parent + * Parent. + * @param style + * Style. + * @param dateStorageLabelType + * Storage label type. + * @see Composite#Composite(Composite, int) + */ + public DateStorageLabelComposite(Composite parent, int style, AbstractStorageLabelType dateStorageLabelType) { + this(parent, style, dateStorageLabelType, true); + } + + /** + * Secondary constructor. Defines if the label should be displayed in the composite. + * + * @param parent + * Parent. + * @param style + * Style. + * @param dateStorageLabelType + * Storage label type. + * @param showLabel + * Should label be displayed next to the selection widget. + * @see Composite#Composite(Composite, int) + */ + public DateStorageLabelComposite(Composite parent, int style, AbstractStorageLabelType dateStorageLabelType, boolean showLabel) { + super(parent, style); + this.dateStorageLabelType = dateStorageLabelType; + this.showLabel = showLabel; + initComposite(); + } + + /** + * Initializes the composite. + */ + private void initComposite() { + if (showLabel) { + GridLayout gl = new GridLayout(2, false); + this.setLayout(gl); + + new Label(this, SWT.NONE).setText("Enter date:"); + } else { + GridLayout gl = new GridLayout(1, false); + this.setLayout(gl); + } + cDateTime = new CDateTime(this, CDT.BORDER | CDT.DROP_DOWN | CDT.TAB_FIELDS); + cDateTime.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + cDateTime.setSelection(new Date()); + } + + /** + * {@inheritDoc} + */ + @Override + public AbstractStorageLabel getStorageLabel() { + return new DateStorageLabel(cDateTime.getSelection(), dateStorageLabelType); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isInputValid() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void addListener(Listener pageCompletionListener) { + cDateTime.addListener(SWT.Modify, pageCompletionListener); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/impl/NumberStorageLabelComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/impl/NumberStorageLabelComposite.java new file mode 100644 index 000000000..790e2e5bb --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/impl/NumberStorageLabelComposite.java @@ -0,0 +1,138 @@ +package info.novatec.inspectit.rcp.storage.label.composite.impl; + +import info.novatec.inspectit.rcp.storage.label.composite.AbstractStorageLabelComposite; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.label.NumberStorageLabel; +import info.novatec.inspectit.storage.label.type.AbstractStorageLabelType; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Text; + +/** + * Composite for selecting the {@link NumberStorageLabel}. + * + * @author Ivan Senic + * + */ +public class NumberStorageLabelComposite extends AbstractStorageLabelComposite { + + /** + * Label type. + */ + private AbstractStorageLabelType numberLabelType; + + /** + * Text box for entering number. + */ + private Text textBox; + + /** + * Should a label be displayed next to the selection widget. + */ + private boolean showLabel; + + /** + * Default constructor. + * + * @param parent + * Parent. + * @param style + * Style. + * @param numberLabelType + * Storage label type. + * @see Composite#Composite(Composite, int) + */ + public NumberStorageLabelComposite(Composite parent, int style, AbstractStorageLabelType numberLabelType) { + this(parent, style, numberLabelType, true); + } + + /** + * Secondary constructor. Defines if the label should be displayed in the composite. + * + * @param parent + * Parent. + * @param style + * Style. + * @param numberLabelType + * Storage label type. + * @param showLabel + * Should label be displayed next to the selection widget. + * @see Composite#Composite(Composite, int) + */ + public NumberStorageLabelComposite(Composite parent, int style, AbstractStorageLabelType numberLabelType, boolean showLabel) { + super(parent, style); + this.numberLabelType = numberLabelType; + this.showLabel = showLabel; + initComposite(); + } + + /** + * Initializes the composite. + */ + private void initComposite() { + if (showLabel) { + GridLayout gl = new GridLayout(2, false); + this.setLayout(gl); + + new Label(this, SWT.NONE).setText("Enter number:"); + } else { + GridLayout gl = new GridLayout(1, false); + this.setLayout(gl); + } + textBox = new Text(this, SWT.BORDER | SWT.RIGHT); + textBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + } + + /** + * {@inheritDoc} + */ + @Override + public AbstractStorageLabel getStorageLabel() { + String text = textBox.getText().trim(); + if (text.indexOf('.') != -1) { + return new NumberStorageLabel(Double.parseDouble(text), numberLabelType); + } else { + return new NumberStorageLabel(Integer.parseInt(text), numberLabelType); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isInputValid() { + String text = textBox.getText().trim(); + if (text.isEmpty()) { + return false; + } + if (text.indexOf('.') != -1) { + try { + Double.parseDouble(text); + return true; + } catch (NumberFormatException e) { + return false; + } + } else { + try { + Integer.parseInt(text); + return true; + } catch (NumberFormatException e) { + return false; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addListener(Listener pageCompletionListener) { + textBox.addListener(SWT.Modify, pageCompletionListener); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/impl/StringStorageLabelComposite.java b/inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/impl/StringStorageLabelComposite.java new file mode 100644 index 000000000..6d2cdfc8b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/storage/label/composite/impl/StringStorageLabelComposite.java @@ -0,0 +1,116 @@ +package info.novatec.inspectit.rcp.storage.label.composite.impl; + +import info.novatec.inspectit.rcp.storage.label.composite.AbstractStorageLabelComposite; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.label.StringStorageLabel; +import info.novatec.inspectit.storage.label.type.AbstractStorageLabelType; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Text; + +/** + * Composite for selecting the {@link StorageStorageLabel}. + * + * @author Ivan Senic + * + */ +public class StringStorageLabelComposite extends AbstractStorageLabelComposite { + + /** + * Label type. + */ + private AbstractStorageLabelType stringLabelType; + + /** + * Text box for entering string. + */ + private Text textBox; + + /** + * Should a label be displayed next to the selection widget. + */ + private boolean showLabel; + + /** + * Default constructor. + * + * @param parent + * Parent. + * @param style + * Style. + * @param stringLabelType + * Storage label type. + * @see Composite#Composite(Composite, int) + */ + public StringStorageLabelComposite(Composite parent, int style, AbstractStorageLabelType stringLabelType) { + this(parent, style, stringLabelType, true); + } + + /** + * Secondary constructor. Defines if the label should be displayed in the composite. + * + * @param parent + * Parent. + * @param style + * Style. + * @param stringLabelType + * Storage label type. + * @param showLabel + * Should label be displayed next to the selection widget. + * @see Composite#Composite(Composite, int) + */ + public StringStorageLabelComposite(Composite parent, int style, AbstractStorageLabelType stringLabelType, boolean showLabel) { + super(parent, style); + this.stringLabelType = stringLabelType; + this.showLabel = showLabel; + initComposite(); + } + + /** + * Initializes the composite. + */ + private void initComposite() { + if (showLabel) { + GridLayout gl = new GridLayout(2, false); + this.setLayout(gl); + + new Label(this, SWT.NONE).setText("Enter text:"); + } else { + GridLayout gl = new GridLayout(1, false); + this.setLayout(gl); + } + + textBox = new Text(this, SWT.BORDER); + textBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + } + + /** + * {@inheritDoc} + */ + @Override + public AbstractStorageLabel getStorageLabel() { + return new StringStorageLabel(textBox.getText().trim(), stringLabelType); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isInputValid() { + return !textBox.getText().trim().isEmpty(); + } + + /** + * {@inheritDoc} + */ + @Override + public void addListener(Listener pageCompletionListener) { + textBox.addListener(SWT.Modify, pageCompletionListener); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/storage/label/edit/LabelValueEditingSupport.java b/inspectIT/src/info/novatec/inspectit/rcp/storage/label/edit/LabelValueEditingSupport.java new file mode 100644 index 000000000..305808e85 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/storage/label/edit/LabelValueEditingSupport.java @@ -0,0 +1,404 @@ +package info.novatec.inspectit.rcp.storage.label.edit; + +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.storage.label.composite.AbstractStorageLabelComposite; +import info.novatec.inspectit.rcp.storage.label.composite.impl.BooleanStorageLabelComposite; +import info.novatec.inspectit.rcp.storage.label.composite.impl.DateStorageLabelComposite; +import info.novatec.inspectit.rcp.storage.label.composite.impl.NumberStorageLabelComposite; +import info.novatec.inspectit.rcp.storage.label.composite.impl.StringStorageLabelComposite; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.label.BooleanStorageLabel; +import info.novatec.inspectit.storage.label.type.AbstractStorageLabelType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Objects; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.viewers.CellEditor; +import org.eclipse.jface.viewers.ComboBoxCellEditor; +import org.eclipse.jface.viewers.EditingSupport; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; + +/** + * Editing support for the values of the table. + * + * @author Ivan Senic + * + */ +public class LabelValueEditingSupport extends EditingSupport { + + /** + * {@link StorageData} needed for updating. + */ + private StorageData storageData; + + /** + * {@link CmrRepositoryDefinition} needed for updating. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * Main cell editor. + */ + private ComboBoxCellEditor generalCellEditor; + + /** + * Cell editor for the boolean valued labels. + */ + private ComboBoxCellEditor booleanCellEditor; + + /** + * Table that is being edited. + */ + private Table table; + + /** + * Suggestion label list. + */ + private List> suggestionLabelList = new ArrayList>(); + + /** + * Label that is being edited. + */ + private AbstractStorageLabel editingLabel; + + /** + * List of listeners. + */ + private List labelEditListeners = new ArrayList(); + + /** + * Default constructor. + * + * @param viewer + * Viewer. + * @param storageData + * {@link StorageData}. + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} + */ + public LabelValueEditingSupport(TableViewer viewer, StorageData storageData, CmrRepositoryDefinition cmrRepositoryDefinition) { + super(viewer); + this.storageData = storageData; + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + table = viewer.getTable(); + generalCellEditor = new ComboBoxCellEditor(table, new String[0], SWT.READ_ONLY); + booleanCellEditor = new ComboBoxCellEditor(table, new String[] { "Yes", "No" }, SWT.READ_ONLY); + + int activationStyle = ComboBoxCellEditor.DROP_DOWN_ON_KEY_ACTIVATION | ComboBoxCellEditor.DROP_DOWN_ON_MOUSE_ACTIVATION | ComboBoxCellEditor.DROP_DOWN_ON_TRAVERSE_ACTIVATION + | ComboBoxCellEditor.DROP_DOWN_ON_PROGRAMMATIC_ACTIVATION; + generalCellEditor.setActivationStyle(activationStyle); + booleanCellEditor.setActivationStyle(activationStyle); + } + + /** + * {@inheritDoc} + */ + @Override + protected CellEditor getCellEditor(Object element) { + AbstractStorageLabel label = (AbstractStorageLabel) element; + if (label instanceof BooleanStorageLabel) { + return booleanCellEditor; + } else { + List items = new ArrayList(); + items.add("[create new value]"); + if (!Objects.equals(label, editingLabel)) { + // if we still update the same label we don't need to reload everything + if (label.getStorageLabelType().isValueReusable() && cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + suggestionLabelList.clear(); + suggestionLabelList.addAll(cmrRepositoryDefinition.getStorageService().getLabelSuggestions(label.getStorageLabelType())); + if (!suggestionLabelList.isEmpty() && null != storageData) { + suggestionLabelList.removeAll(storageData.getLabelList()); + } + } + } + + if (!suggestionLabelList.isEmpty()) { + Collections.sort(suggestionLabelList); + for (AbstractStorageLabel existingLabel : suggestionLabelList) { + items.add(TextFormatter.getLabelValue(existingLabel, false)); + } + } + + generalCellEditor.setItems(items.toArray(new String[items.size()])); + return generalCellEditor; + } + } + + /** + * {@inheritDoc} + */ + @Override + protected boolean canEdit(Object element) { + AbstractStorageLabel label = (AbstractStorageLabel) element; + return label.getStorageLabelType().isEditable(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Object getValue(Object element) { + AbstractStorageLabel label = (AbstractStorageLabel) element; + editingLabel = label; + if (null != label.getValue()) { + if (label instanceof BooleanStorageLabel) { + if (((BooleanStorageLabel) label).getValue().booleanValue()) { + return 0; + } else { + return 1; + } + } else { + String value = TextFormatter.getLabelValue(label, false); + int i = 1; + for (AbstractStorageLabel suggestionLabel : suggestionLabelList) { + if (Objects.equals(value, TextFormatter.getLabelValue(suggestionLabel, false))) { + return i; + } + i++; + } + } + } + return -1; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + protected void setValue(Object element, Object value) { + int index = ((Integer) value).intValue(); + if (index >= 0) { + AbstractStorageLabel label = (AbstractStorageLabel) element; + if (label.getStorageLabelType().getValueClass().equals(Boolean.class)) { + Boolean newValue = Boolean.valueOf(0 == index); + changeValue(label, newValue); + } else { + if (index == 0) { + // create new + CreateValueDialog createValueDialog = new CreateValueDialog(table.getShell(), label.getStorageLabelType()); + createValueDialog.open(); + if (createValueDialog.getReturnCode() == Dialog.OK) { + AbstractStorageLabel createdLabel = createValueDialog.getCreatedLabel(); + changeValue(label, createdLabel.getValue()); + if (!suggestionLabelList.contains(createdLabel)) { + suggestionLabelList.add(createdLabel); + } + } + + } else if (index > 0) { + AbstractStorageLabel suggestionLabel = suggestionLabelList.get(index - 1); + Object labelValue = suggestionLabel.getValue(); + changeValue(label, labelValue); + } + + } + getViewer().refresh(); + } + } + + /** + * Reforms the value change and informs the listeners. + * + * @param label + * Label to have value changed. + * @param newValue + * New value to set. + */ + private void changeValue(AbstractStorageLabel label, Object newValue) { + if (!Objects.equals(label.getValue(), newValue)) { + synchronized (labelEditListeners) { + for (LabelEditListener listener : labelEditListeners) { + listener.preLabelValueChange(label); + } + } + + label.setValue(newValue); + + synchronized (labelEditListeners) { + for (LabelEditListener listener : labelEditListeners) { + listener.postLabelValueChange(label); + } + } + } + } + + /** + * Adds a {@link LabelEditListener}. + * + * @param listener + * Listener to add. + */ + public void addLabelEditListener(LabelEditListener listener) { + synchronized (labelEditListeners) { + if (!labelEditListeners.contains(listener)) { + labelEditListeners.add(listener); + } + } + } + + /** + * Removes a {@link LabelEditListener}. + * + * @param listener + * Listener to remove. + */ + public void removeLabelEditListener(LabelEditListener listener) { + synchronized (labelEditListeners) { + labelEditListeners.remove(listener); + } + } + + /** + * Listener interface that will be called prior to and after label value editing. + * + * @author Ivan Senic + * + */ + public interface LabelEditListener { + + /** + * Called before the label is changed. + * + * @param label + * {@link AbstractStorageLabel}. + */ + void preLabelValueChange(AbstractStorageLabel label); + + /** + * Called after the label is changed. + * + * @param label + * {@link AbstractStorageLabel}. + */ + void postLabelValueChange(AbstractStorageLabel label); + + } + + /** + * Dialog for creating new values. + * + * @author Ivan Senic + * + */ + private static class CreateValueDialog extends Dialog { + + /** + * Label type. + */ + private AbstractStorageLabelType labelType; + + /** + * Label to hold the created value. + */ + private AbstractStorageLabel label; + + /** + * Composite for definition. + */ + private AbstractStorageLabelComposite storageLabelComposite; + + /** + * OK button. + */ + private Button okButton; + + /** + * Default constructor. + * + * @param parentShell + * Shell. + * @param labelType + * Type of label. + */ + protected CreateValueDialog(Shell parentShell, AbstractStorageLabelType labelType) { + super(parentShell); + this.labelType = labelType; + } + + /** + * {@inheritDoc} + */ + @Override + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + newShell.setText("Create New Label Value"); + } + + /** + * {@inheritDoc} + */ + @Override + protected void createButtonsForButtonBar(Composite parent) { + createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, true); + okButton = createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); + okButton.setEnabled(storageLabelComposite.isInputValid()); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + protected Control createDialogArea(Composite parent) { + if (labelType.getValueClass().equals(Boolean.class)) { + storageLabelComposite = new BooleanStorageLabelComposite(parent, SWT.NONE, (AbstractStorageLabelType) labelType); + } else if (labelType.getValueClass().equals(Date.class)) { + storageLabelComposite = new DateStorageLabelComposite(parent, SWT.NONE, (AbstractStorageLabelType) labelType); + } else if (labelType.getValueClass().equals(Number.class)) { + storageLabelComposite = new NumberStorageLabelComposite(parent, SWT.NONE, (AbstractStorageLabelType) labelType); + } else if (labelType.getValueClass().equals(String.class)) { + storageLabelComposite = new StringStorageLabelComposite(parent, SWT.NONE, (AbstractStorageLabelType) labelType); + } + + GridData gd = new GridData(SWT.FILL, SWT.FILL, true, false); + gd.minimumWidth = 300; + storageLabelComposite.setLayoutData(gd); + storageLabelComposite.addListener(new Listener() { + @Override + public void handleEvent(Event event) { + okButton.setEnabled(storageLabelComposite.isInputValid()); + } + }); + return storageLabelComposite; + } + + /** + * {@inheritDoc} + */ + @Override + protected void buttonPressed(int buttonId) { + if (Dialog.OK == buttonId) { + label = storageLabelComposite.getStorageLabel(); + } + super.buttonPressed(buttonId); + } + + /** + * Gets {@link #label}. + * + * @return {@link #label} + */ + public AbstractStorageLabel getCreatedLabel() { + return label; + } + + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/storage/listener/StorageChangeListener.java b/inspectIT/src/info/novatec/inspectit/rcp/storage/listener/StorageChangeListener.java new file mode 100644 index 000000000..ad1f4f47c --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/storage/listener/StorageChangeListener.java @@ -0,0 +1,38 @@ +package info.novatec.inspectit.rcp.storage.listener; + +import info.novatec.inspectit.storage.IStorageData; + +import java.util.EventListener; + +/** + * Storage change listener. + * + * @author Ivan Senic + * + */ +public interface StorageChangeListener extends EventListener { + + /** + * Informs the listener that the storage data like name or description have been updated. + * + * @param storageData + * {@link IStorageData}. + */ + void storageDataUpdated(IStorageData storageData); + + /** + * Informs the listener that the repository was deleted on the CMR. + * + * @param storageData + * {@link IStorageData}. + */ + void storageRemotelyDeleted(IStorageData storageData); + + /** + * Informs the listener that the repository was deleted locally. + * + * @param storageData + * {@link IStorageData}. + */ + void storageLocallyDeleted(IStorageData storageData); +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/storage/util/DataRetriever.java b/inspectIT/src/info/novatec/inspectit/rcp/storage/util/DataRetriever.java new file mode 100644 index 000000000..f5499126e --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/storage/util/DataRetriever.java @@ -0,0 +1,791 @@ +package info.novatec.inspectit.rcp.storage.util; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.indexing.storage.IStorageDescriptor; +import info.novatec.inspectit.indexing.storage.impl.StorageDescriptor; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.storage.http.TransferDataMonitor; +import info.novatec.inspectit.storage.IStorageData; +import info.novatec.inspectit.storage.LocalStorageData; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.StorageFileType; +import info.novatec.inspectit.storage.StorageManager; +import info.novatec.inspectit.storage.nio.stream.InputStreamProvider; +import info.novatec.inspectit.storage.serializer.ISerializer; +import info.novatec.inspectit.storage.serializer.SerializationException; +import info.novatec.inspectit.storage.serializer.provider.SerializationManagerProvider; +import info.novatec.inspectit.storage.serializer.util.KryoUtil; +import info.novatec.inspectit.storage.util.RangeDescriptor; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.zip.GZIPInputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.apache.commons.collections.MapUtils; +import org.apache.commons.fileupload.MultipartStream; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.ArrayUtils; +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.StatusLine; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.entity.HttpEntityWrapper; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.protocol.HttpContext; +import org.apache.http.util.EntityUtils; +import org.eclipse.core.runtime.SubMonitor; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatus.Series; + +import com.esotericsoftware.kryo.io.Input; + +/** + * Class responsible for retrieving the data via HTTP, and de-serializing the data into objects. + * + * @author Ivan Senic + * + */ +public class DataRetriever { + + /** + * Amount of serializers to be available to this class. + */ + private int serializerCount = 3; + + /** + * {@link StorageManager}. + */ + private StorageManager storageManager; + + /** + * Serialization manager provider. + */ + private SerializationManagerProvider serializationManagerProvider; + + /** + * Queue for {@link ISerializer} that are available. + */ + private BlockingQueue serializerQueue = new LinkedBlockingQueue(); + + /** + * Stream provider needed for local data reading. + */ + private InputStreamProvider streamProvider; + + /** + * Initializes the retriever. + * + * @throws Exception + * If exception occurs. + */ + protected void init() throws Exception { + for (int i = 0; i < serializerCount; i++) { + serializerQueue.add(serializationManagerProvider.createSerializer()); + } + } + + /** + * Retrieves the wanted data described in the {@link StorageDescriptor} from the desired + * {@link CmrRepositoryDefinition}. This method will try to invoke as less as possible HTTP + * requests for all descriptors. + *

+ * The method will execute the HTTP requests sequentially. + *

+ * It is not guaranteed that amount of returned objects in the list is same as the amount of + * provided descriptors. If some of the descriptors are pointing to the wrong files or files + * positions, it can happen that this influences the rest of the descriptor that point to the + * same file. Thus, a special care needs to be taken that the data in descriptors is correct. + * + * @param + * Type of the objects are wanted. + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + * @param storageData + * {@link StorageData} that points to the wanted storage. + * @param descriptors + * Descriptors. + * @return List of objects in the supplied generic type. Note that if the data described in the + * descriptor is not of a supplied generic type, there will be a casting exception + * thrown. + * @throws SerializationException + * If {@link SerializationException} occurs. + * @throws IOException + * If {@link IOException} occurs. + */ + @SuppressWarnings("unchecked") + public List getDataViaHttp(CmrRepositoryDefinition cmrRepositoryDefinition, IStorageData storageData, List descriptors) throws IOException, + SerializationException { + Map> separateFilesGroup = createFilesGroup(descriptors); + List receivedData = new ArrayList(); + String serverUri = getServerUri(cmrRepositoryDefinition); + + HttpClient httpClient = new DefaultHttpClient(); + for (Map.Entry> entry : separateFilesGroup.entrySet()) { + HttpGet httpGet = new HttpGet(serverUri + storageManager.getHttpFileLocation(storageData, entry.getKey())); + StringBuilder rangeHeader = new StringBuilder("bytes="); + + RangeDescriptor rangeDescriptor = null; + for (IStorageDescriptor descriptor : entry.getValue()) { + if (null == rangeDescriptor) { + rangeDescriptor = new RangeDescriptor(descriptor); + } else { + if (rangeDescriptor.getEnd() + 1 == descriptor.getPosition()) { + rangeDescriptor.setEnd(descriptor.getPosition() + descriptor.getSize() - 1); + } else { + rangeHeader.append(rangeDescriptor.toString()); + rangeHeader.append(','); + rangeDescriptor = new RangeDescriptor(descriptor); + } + } + } + rangeHeader.append(rangeDescriptor); + + httpGet.addHeader("Range", rangeHeader.toString()); + ISerializer serializer = null; + try { + serializer = serializerQueue.take(); + } catch (InterruptedException e) { + Thread.interrupted(); + } + InputStream inputStream = null; + Input input = null; + try { + HttpResponse response = httpClient.execute(httpGet); + HttpEntity entity = response.getEntity(); + if (MultipartEntityUtil.isMultipart(entity)) { + inputStream = entity.getContent(); + @SuppressWarnings("deprecation") + // all non-deprecated constructors have default modifier + MultipartStream multipartStream = new MultipartStream(inputStream, MultipartEntityUtil.getBoundary(entity).getBytes()); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + boolean nextPart = multipartStream.skipPreamble(); + while (nextPart) { + multipartStream.readHeaders(); + multipartStream.readBodyData(byteArrayOutputStream); + input = new Input(byteArrayOutputStream.toByteArray()); + while (KryoUtil.hasMoreBytes(input)) { + Object object = serializer.deserialize(input); + E element = (E) object; + receivedData.add(element); + } + nextPart = multipartStream.readBoundary(); + } + } else { + // when kryo changes the visibility of optional() method, we can really stream + input = new Input(EntityUtils.toByteArray(entity)); + while (KryoUtil.hasMoreBytes(input)) { + Object object = serializer.deserialize(input); + E element = (E) object; + receivedData.add(element); + } + } + } finally { + if (null != inputStream) { + inputStream.close(); + } + if (null != input) { + input.close(); + } + serializerQueue.add(serializer); + } + } + return receivedData; + } + + /** + * Retrieves the wanted data described in the {@link StorageDescriptor} from the desired + * offline-available storage. + *

+ * It is not guaranteed that amount of returned objects in the list is same as the amount of + * provided descriptors. If some of the descriptors are pointing to the wrong files or files + * positions, it can happen that this influences the rest of the descriptor that point to the + * same file. Thus, a special care needs to be taken that the data in descriptors is correct. + * + * @param + * Type of the objects are wanted. + * @param localStorageData + * {@link LocalStorageData} that points to the wanted storage. + * @param descriptors + * Descriptors. + * @return List of objects in the supplied generic type. Note that if the data described in the + * descriptor is not of a supplied generic type, there will be a casting exception + * thrown. + * @throws SerializationException + * If {@link SerializationException} occurs. + * @throws IOException + * If {@link IOException} occurs. + */ + @SuppressWarnings("unchecked") + public List getDataLocally(LocalStorageData localStorageData, List descriptors) throws IOException, SerializationException { + Map> separateFilesGroup = createFilesGroup(descriptors); + List optimizedDescriptors = new ArrayList(); + for (Map.Entry> entry : separateFilesGroup.entrySet()) { + StorageDescriptor storageDescriptor = null; + for (IStorageDescriptor descriptor : entry.getValue()) { + if (null == storageDescriptor) { + storageDescriptor = new StorageDescriptor(entry.getKey()); + storageDescriptor.setPositionAndSize(descriptor.getPosition(), descriptor.getSize()); + } else { + if (!storageDescriptor.join(descriptor)) { + optimizedDescriptors.add(storageDescriptor); + storageDescriptor = new StorageDescriptor(entry.getKey()); + storageDescriptor.setPositionAndSize(descriptor.getPosition(), descriptor.getSize()); + } + } + } + optimizedDescriptors.add(storageDescriptor); + } + + List receivedData = new ArrayList(descriptors.size()); + + ISerializer serializer = null; + try { + serializer = serializerQueue.take(); + } catch (InterruptedException e) { + Thread.interrupted(); + } + InputStream inputStream = null; + Input input = null; + try { + inputStream = streamProvider.getExtendedByteBufferInputStream(localStorageData, optimizedDescriptors); + input = new Input(inputStream); + while (KryoUtil.hasMoreBytes(input)) { + Object object = serializer.deserialize(input); + E element = (E) object; + receivedData.add(element); + } + } finally { + if (null != input) { + input.close(); + } + serializerQueue.add(serializer); + } + + return receivedData; + } + + /** + * Returns cached data for the storage from the CMR if the cached data exists for given hash. If + * data does not exist null is returned. + * + * @param + * Type of the objects are wanted. + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + * @param storageData + * {@link StorageData} that points to the wanted storage. + * @param hash + * Hash under which the cached data is stored. + * @return Returns cached data for the storage from the CMR if the cached data exists for given + * hash. If data does not exist null is returned. + * @throws StorageException + * If {@link StorageException} occurred. + * @throws SerializationException + * If {@link SerializationException} occurs. + * @throws IOException + * If {@link IOException} occurs. + */ + @SuppressWarnings("unchecked") + public List getCachedDataViaHttp(CmrRepositoryDefinition cmrRepositoryDefinition, StorageData storageData, int hash) throws StorageException, IOException, + SerializationException { + String cachedFileLocation = cmrRepositoryDefinition.getStorageService().getCachedStorageDataFileLocation(storageData, hash); + if (null == cachedFileLocation) { + return null; + } else { + HttpClient httpClient = new DefaultHttpClient(); + HttpGet httpGet = new HttpGet(getServerUri(cmrRepositoryDefinition) + cachedFileLocation); + ISerializer serializer = null; + try { + serializer = serializerQueue.take(); + } catch (InterruptedException e) { + Thread.interrupted(); + } + InputStream inputStream = null; + Input input = null; + try { + HttpResponse response = httpClient.execute(httpGet); + HttpEntity entity = response.getEntity(); + inputStream = entity.getContent(); + input = new Input(inputStream); + Object object = serializer.deserialize(input); + List receivedData = (List) object; + return receivedData; + } finally { + if (null != inputStream) { + inputStream.close(); + } + if (null != input) { + input.close(); + } + serializerQueue.add(serializer); + } + } + } + + /** + * Returns cached data for the given hash locally. This method can be used when storage if fully + * downloaded. + * + * @param + * Type of the objects are wanted. + * + * @param localStorageData + * {@link LocalStorageData} that points to the wanted storage. + * @param hash + * Hash under which the cached data is stored. + * @return Returns cached data for the storage if the cached data exists for given hash. If data + * does not exist null is returned. + * @throws SerializationException + * If {@link SerializationException} occurs. + * @throws IOException + * If {@link IOException} occurs. + */ + @SuppressWarnings("unchecked") + public List getCachedDataLocally(LocalStorageData localStorageData, int hash) throws IOException, SerializationException { + Path path = storageManager.getCachedDataPath(localStorageData, hash); + if (Files.notExists(path)) { + return null; + } else { + ISerializer serializer = null; + try { + serializer = serializerQueue.take(); + } catch (InterruptedException e) { + Thread.interrupted(); + } + + Input input = null; + try (InputStream inputStream = Files.newInputStream(path, StandardOpenOption.READ)) { + input = new Input(inputStream); + Object object = serializer.deserialize(input); + List receivedData = (List) object; + return receivedData; + } finally { + if (null != input) { + input.close(); + } + serializerQueue.add(serializer); + } + } + } + + /** + * Downloads and saves locally wanted files associated with given {@link StorageData}. Files + * will be saved in passed directory. The caller can specify the type of the files to download + * by passing the proper {@link StorageFileType}s to the method. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + * @param storageData + * {@link StorageData}. + * @param directory + * Directory to save objects. compressBefore Should data files be compressed on the + * fly before sent. + * @param compressBefore + * Should data files be compressed on the fly before sent. + * @param decompressContent + * If the useGzipCompression is true, this parameter will define if the + * received content will be de-compressed. If false is passed content will be saved + * to file in the same format as received, but the path of the file will be altered + * with additional '.gzip' extension at the end. + * @param subMonitor + * {@link SubMonitor} for process reporting. + * @param fileTypes + * Files that should be downloaded. + * @throws StorageException + * If directory to save does not exists. If files wanted can not be found on the + * server. + * @throws IOException + * If {@link IOException} occurs. + */ + public void downloadAndSaveStorageFiles(CmrRepositoryDefinition cmrRepositoryDefinition, StorageData storageData, final Path directory, boolean compressBefore, boolean decompressContent, + SubMonitor subMonitor, StorageFileType... fileTypes) throws StorageException, IOException { + if (!Files.isDirectory(directory)) { + throw new StorageException("Directory path supplied as the data saving destination is not valid. Given path is: " + directory.toString()); + } + + Map allFiles = getFilesFromCmr(cmrRepositoryDefinition, storageData, fileTypes); + + if (MapUtils.isNotEmpty(allFiles)) { + PostDownloadRunnable postDownloadRunnable = new PostDownloadRunnable() { + @Override + public void process(InputStream content, String fileName) throws IOException { + String[] splittedFileName = fileName.split("/"); + Path writePath = directory; + // first part is empty, second is storage id, we don't need it + for (int i = 2; i < splittedFileName.length; i++) { + writePath = writePath.resolve(splittedFileName[i]); + } + // ensure all dirs are created + if (Files.notExists(writePath.getParent())) { + Files.createDirectories(writePath.getParent()); + } + Files.copy(content, writePath, StandardCopyOption.REPLACE_EXISTING); + } + }; + this.downloadAndSaveObjects(cmrRepositoryDefinition, allFiles, postDownloadRunnable, compressBefore, decompressContent, subMonitor); + } + } + + /** + * Downloads and saves locally wanted files associated with given {@link StorageData}. Files + * will be saved in passed directory. The caller can specify the type of the files to download + * by passing the proper {@link StorageFileType}s to the method. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + * @param storageData + * {@link StorageData}. + * @param zos + * {@link ZipOutputStream} to place files to. + * @param compressBefore + * Should data files be compressed on the fly before sent. + * @param decompressContent + * If the useGzipCompression is true, this parameter will define if the + * received content will be de-compressed. If false is passed content will be saved + * to file in the same format as received, but the path of the file will be altered + * with additional '.gzip' extension at the end. + * @param subMonitor + * {@link SubMonitor} for process reporting. + * @param fileTypes + * Files that should be downloaded. + * @throws StorageException + * If directory to save does not exists. If files wanted can not be found on the + * server. + * @throws IOException + * If {@link IOException} occurs. + */ + public void downloadAndZipStorageFiles(CmrRepositoryDefinition cmrRepositoryDefinition, StorageData storageData, final ZipOutputStream zos, boolean compressBefore, boolean decompressContent, + SubMonitor subMonitor, StorageFileType... fileTypes) throws StorageException, IOException { + Map allFiles = getFilesFromCmr(cmrRepositoryDefinition, storageData, fileTypes); + + PostDownloadRunnable postDownloadRunnable = new PostDownloadRunnable() { + @Override + public void process(InputStream content, String fileName) throws IOException { + String[] splittedFileName = fileName.split("/"); + String originalFileName = splittedFileName[splittedFileName.length - 1]; + ZipEntry zipEntry = new ZipEntry(originalFileName); + zos.putNextEntry(zipEntry); + IOUtils.copy(content, zos); + zos.closeEntry(); + } + }; + this.downloadAndSaveObjects(cmrRepositoryDefinition, allFiles, postDownloadRunnable, compressBefore, decompressContent, subMonitor); + } + + /** + * Returns the map of the existing files for the given storage. The value in the map is file + * size. Only wanted file types will be included in the map. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + * @param storageData + * {@link StorageData} + * @param fileTypes + * Files that should be included. + * @return Map of file names with their size. + * @throws StorageException + * If {@link StorageException} occurs during service invocation. + */ + private Map getFilesFromCmr(CmrRepositoryDefinition cmrRepositoryDefinition, StorageData storageData, StorageFileType... fileTypes) throws StorageException { + Map allFiles = new HashMap(); + + // agent files + if (ArrayUtils.contains(fileTypes, StorageFileType.AGENT_FILE)) { + Map platformIdentsFiles = cmrRepositoryDefinition.getStorageService().getAgentFilesLocations(storageData); + allFiles.putAll(platformIdentsFiles); + } + + // indexing files + if (ArrayUtils.contains(fileTypes, StorageFileType.INDEX_FILE)) { + Map indexingTreeFiles = cmrRepositoryDefinition.getStorageService().getIndexFilesLocations(storageData); + allFiles.putAll(indexingTreeFiles); + } + + if (ArrayUtils.contains(fileTypes, StorageFileType.DATA_FILE)) { + // data files + Map dataFiles = cmrRepositoryDefinition.getStorageService().getDataFilesLocations(storageData); + allFiles.putAll(dataFiles); + } + + if (ArrayUtils.contains(fileTypes, StorageFileType.CACHED_DATA_FILE)) { + // data files + Map dataFiles = cmrRepositoryDefinition.getStorageService().getCachedDataFilesLocations(storageData); + allFiles.putAll(dataFiles); + } + + return allFiles; + } + + /** + * Down-loads and saves the file from a {@link CmrRepositoryDefinition}. Files will be saved in + * the directory that is denoted as the given Path object. Original file names will be used. + * + * @param cmrRepositoryDefinition + * Repository. + * @param files + * Map with file names and sizes. + * @param postDownloadRunnable + * {@link PostDownloadRunnable} that will be executed after successful request. + * @param useGzipCompression + * If the GZip compression should be used when files are downloaded. + * @param decompressContent + * If the useGzipCompression is true, this parameter will define if the + * received content will be de-compressed. If false is passed content will be saved + * to file in the same format as received, but the path of the file will be altered + * with additional '.gzip' extension at the end. + * @param subMonitor + * {@link SubMonitor} for process reporting. + * @throws IOException + * If {@link IOException} occurs. + * @throws StorageException + * If status of HTTP response is not successful (codes 2xx). + */ + private void downloadAndSaveObjects(CmrRepositoryDefinition cmrRepositoryDefinition, Map files, PostDownloadRunnable postDownloadRunnable, boolean useGzipCompression, + boolean decompressContent, final SubMonitor subMonitor) throws IOException, StorageException { + DefaultHttpClient httpClient = new DefaultHttpClient(); + final TransferDataMonitor transferDataMonitor = new TransferDataMonitor(subMonitor, files, useGzipCompression); + httpClient.addResponseInterceptor(new HttpResponseInterceptor() { + @Override + public void process(HttpResponse response, HttpContext context) throws HttpException, IOException { + response.setEntity(new DownloadHttpEntityWrapper(response.getEntity(), transferDataMonitor)); + } + }); + + if (useGzipCompression && decompressContent) { + httpClient.addResponseInterceptor(new GzipHttpResponseInterceptor()); + } + + for (Map.Entry fileEntry : files.entrySet()) { + String fileName = fileEntry.getKey(); + String fileLocation = getServerUri(cmrRepositoryDefinition) + fileName; + HttpGet httpGet = new HttpGet(fileLocation); + if (useGzipCompression) { + httpGet.addHeader("accept-encoding", "gzip"); + } + + transferDataMonitor.startTransfer(fileName); + HttpResponse response = httpClient.execute(httpGet); + StatusLine statusLine = response.getStatusLine(); + if (HttpStatus.valueOf(statusLine.getStatusCode()).series().equals(Series.SUCCESSFUL)) { + HttpEntity entity = response.getEntity(); + try (InputStream is = entity.getContent()) { + postDownloadRunnable.process(is, fileName); + } + } + transferDataMonitor.endTransfer(fileName); + } + } + + /** + * Returns the URI of the server in format 'http://ip:port'. + * + * @param repositoryDefinition + * {@link CmrRepositoryDefinition}. + * @return URI as string. + */ + private String getServerUri(CmrRepositoryDefinition repositoryDefinition) { + return "http://" + repositoryDefinition.getIp() + ":" + repositoryDefinition.getPort(); + } + + /** + * Creates the pairs that have a channel ID as a key, and list of descriptors as value. All the + * descriptors in the list are associated with the channel, thus all the data described in the + * descriptors can be retrieved with a single HTTP/local request. + * + * @param descriptors + * Un-grouped descriptors. + * + * @return Map of channel IDs with its descriptors. + */ + private Map> createFilesGroup(List descriptors) { + Map> filesMap = new HashMap>(); + for (IStorageDescriptor storageDescriptor : descriptors) { + Integer channelId = Integer.valueOf(storageDescriptor.getChannelId()); + List oneFileList = filesMap.get(channelId); + if (null == oneFileList) { + oneFileList = new ArrayList(); + filesMap.put(channelId, oneFileList); + } + oneFileList.add(storageDescriptor); + } + + // sort lists + for (Map.Entry> entry : filesMap.entrySet()) { + List list = entry.getValue(); + Collections.sort(list, new Comparator() { + + @Override + public int compare(IStorageDescriptor o1, IStorageDescriptor o2) { + return Long.compare(o1.getPosition(), o2.getPosition()); + } + }); + } + + return filesMap; + } + + /** + * Sets {@link #storageManager}. + * + * @param storageManager + * New value for {@link #storageManager} + */ + public void setStorageManager(StorageManager storageManager) { + this.storageManager = storageManager; + } + + /** + * + * @param entity + * {@link HttpEntity} + * @return True if the GZip encoding is active. + */ + private static boolean isGZipContentEncoding(HttpEntity entity) { + Header ceHeader = entity.getContentEncoding(); + if (ceHeader != null) { + HeaderElement[] codecs = ceHeader.getElements(); + for (int i = 0; i < codecs.length; i++) { + if (codecs[i].getName().equalsIgnoreCase("gzip")) { + return true; + } + } + } + return false; + } + + /** + * Sets {@link #serializerCount}. + * + * @param serializerCount + * New value for {@link #serializerCount} + */ + public void setSerializerCount(int serializerCount) { + this.serializerCount = serializerCount; + } + + /** + * Sets {@link #serializationManagerProvider}. + * + * @param serializationManagerProvider + * New value for {@link #serializationManagerProvider} + */ + public void setSerializationManagerProvider(SerializationManagerProvider serializationManagerProvider) { + this.serializationManagerProvider = serializationManagerProvider; + } + + /** + * Sets {@link #streamProvider}. + * + * @param streamProvider + * New value for {@link #streamProvider} + */ + public void setStreamProvider(InputStreamProvider streamProvider) { + this.streamProvider = streamProvider; + } + + /** + * A wrapper for the {@link HttpEntity} that will surround the entity's input stream with the + * {@link GZIPInputStream}. * + *

+ * IMPORTANT: The class code is copied/taken/based from Http Core's GzipDecompressingEntity. License info can be found here. + * + * + */ + private static class GzipDecompressingEntity extends HttpEntityWrapper { + + /** + * Default constructor. + * + * @param entity + * Entity that has the response in the GZip format. + */ + public GzipDecompressingEntity(final HttpEntity entity) { + super(entity); + } + + /** + * {@inheritDoc} + */ + public InputStream getContent() throws IOException { + // the wrapped entity's getContent() decides about repeatability + InputStream wrappedin = wrappedEntity.getContent(); + return new GZIPInputStream(wrappedin); + } + + /** + * {@inheritDoc} + */ + @Override + public long getContentLength() { + // length of uncompressed content is not known + return -1; + } + + } + + /** + * Response interceptor that alters the response entity if the encoding is gzip. + * + * @author Ivan Senic + * + */ + private static class GzipHttpResponseInterceptor implements HttpResponseInterceptor { + + /** + * {@inheritDoc} + */ + @Override + public void process(HttpResponse response, HttpContext context) throws HttpException, IOException { + if (isGZipContentEncoding(response.getEntity())) { + response.setEntity(new GzipDecompressingEntity(response.getEntity())); + } + } + + } + + /** + * Simple interface to enable multiple operations after file download. + * + * @author Ivan Senic + * + */ + private interface PostDownloadRunnable { + + /** + * Process the input stream. If stream is not closed, it will be after exiting this method. + * + * @param content + * {@link InputStream} that represents content of downloaded file. + * @param fileName + * Name of the file being downloaded. + * @throws IOException + * If {@link IOException} occurs. + */ + void process(InputStream content, String fileName) throws IOException; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/storage/util/DataUploader.java b/inspectIT/src/info/novatec/inspectit/rcp/storage/util/DataUploader.java new file mode 100644 index 000000000..85d398827 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/storage/util/DataUploader.java @@ -0,0 +1,136 @@ +package info.novatec.inspectit.rcp.storage.util; + +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.storage.http.TransferDataMonitor; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntity; +import org.apache.http.entity.mime.content.FileBody; +import org.apache.http.impl.client.DefaultHttpClient; +import org.eclipse.core.runtime.SubMonitor; + +/** + * Utility class for uploading data. + * + * @author Ivan Senic + * + */ +public class DataUploader { + + /** + * Upload servlet mapping. + */ + private static final String UPLOAD_SERVLET = "/fileupload"; + + /** + * Uploads the file to the {@link CmrRepositoryDefinition} storage upload folder. + *

+ * File will be uploaded to the upload folder with the suggested path that is relative + * relativizePath, with addition of tmpDir to the beginning of the path. + * + * @param fileToUpload + * File to upload. + * @param relativizePath + * Path to relativize file path. + * @param tmpDir + * Sub-directory in the upload folder to put files into. + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + * @param subMonitor + * {@link SubMonitor} to report progress to. + * @throws Exception + * If file to upload does not exist or exception occurs during the upload. + */ + public void uploadFileToStorageUploads(Path fileToUpload, Path relativizePath, String tmpDir, CmrRepositoryDefinition cmrRepositoryDefinition, SubMonitor subMonitor) throws Exception { + this.uploadFileToStorageUploads(Collections.singletonList(fileToUpload), relativizePath, tmpDir, cmrRepositoryDefinition, subMonitor); + } + + /** + * Uploads the files to the {@link CmrRepositoryDefinition} storage upload folder. + *

+ * Every file will be uploaded with the suggested path that is relative relativizePath, with + * addition of tmpDir to the beginning of the path. + * + * @param filesToUpload + * Files to upload as path list. + * @param relativizePath + * Path to relativize file path. + * @param tmpDir + * Sub-directory in the upload folder to put files into. + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + * @param subMonitor + * {@link SubMonitor} to report progress to. + * @throws Exception + * If file to upload does not exist or exception occurs during the upload. + */ + public void uploadFileToStorageUploads(List filesToUpload, Path relativizePath, String tmpDir, CmrRepositoryDefinition cmrRepositoryDefinition, SubMonitor subMonitor) throws Exception { + // calculate how much is there to upload + Map files = new HashMap(); + for (Path file : filesToUpload) { + if (Files.notExists(file)) { + throw new IOException("File to upload (" + file + ") does not exist."); + } + files.put(file.toString(), Files.size(file)); + } + TransferDataMonitor transferDataMonitor = new TransferDataMonitor(subMonitor, files, false); + + // prepare uri + String uri = getServerUri(cmrRepositoryDefinition) + UPLOAD_SERVLET; + + // execute post for each file + for (Path file : filesToUpload) { + DefaultHttpClient httpClient = null; + try { + httpClient = new DefaultHttpClient(); + HttpPost httpPost = new HttpPost(uri); + // create entity + MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); + if (Files.notExists(file)) { + throw new IOException("File to upload (" + file + ") does not exist."); + } + FileBody bin = new FileBody(file.toFile()); + StringBuilder pathString = new StringBuilder(relativizePath.relativize(file).toString()); + if (null != tmpDir) { + pathString.insert(0, tmpDir + File.separator); + } + entity.addPart(pathString.toString(), bin); + // wrap entity + UploadHttpEntityWrapper entityWrapper = new UploadHttpEntityWrapper(entity, transferDataMonitor); + httpPost.setEntity(entityWrapper); + + // execute upload + transferDataMonitor.startTransfer(file.toString()); + httpClient.execute(httpPost); + transferDataMonitor.endTransfer(file.toString()); + } finally { + if (null != httpClient) { + httpClient.getConnectionManager().shutdown(); + } + } + } + + } + + /** + * Returns the URI of the server in format 'http://ip:port'. + * + * @param repositoryDefinition + * {@link CmrRepositoryDefinition}. + * @return URI as string. + */ + private String getServerUri(CmrRepositoryDefinition repositoryDefinition) { + return "http://" + repositoryDefinition.getIp() + ":" + repositoryDefinition.getPort(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/storage/util/DownloadHttpEntityWrapper.java b/inspectIT/src/info/novatec/inspectit/rcp/storage/util/DownloadHttpEntityWrapper.java new file mode 100644 index 000000000..c53600853 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/storage/util/DownloadHttpEntityWrapper.java @@ -0,0 +1,47 @@ +package info.novatec.inspectit.rcp.storage.util; + +import info.novatec.inspectit.rcp.storage.http.TransferDataMonitor; +import info.novatec.inspectit.rcp.storage.http.TransferRateInputStream; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.http.HttpEntity; +import org.apache.http.entity.HttpEntityWrapper; + +/** + * Wrapping entity to support download speed monitoring. + * + * @author Ivan Senic + * + */ +public class DownloadHttpEntityWrapper extends HttpEntityWrapper { + + /** + * {@link TransferDataMonitor} to pass to the {@link TransferRateInputStream}. + */ + private TransferDataMonitor transferDataMonitor; + + /** + * Default constructor. + * + * @param wrapped + * Entity to be wrapped. + * @param transferDataMonitor + * {@link TransferDataMonitor} to pass to the {@link TransferRateInputStream}. + */ + public DownloadHttpEntityWrapper(HttpEntity wrapped, TransferDataMonitor transferDataMonitor) { + super(wrapped); + this.transferDataMonitor = transferDataMonitor; + } + + /** + * {@inheritDoc} + */ + @Override + public InputStream getContent() throws IOException { + InputStream wrappedin = wrappedEntity.getContent(); + return new TransferRateInputStream(wrappedin, transferDataMonitor); + } + +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/storage/util/MultipartEntityUtil.java b/inspectIT/src/info/novatec/inspectit/rcp/storage/util/MultipartEntityUtil.java new file mode 100644 index 000000000..f613e800b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/storage/util/MultipartEntityUtil.java @@ -0,0 +1,63 @@ +package info.novatec.inspectit.rcp.storage.util; + +import java.util.StringTokenizer; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; + +/** + * {@link MultipartEntityUtil} provides simple methods for multipart entities. + * + * @author Ivan Senic + * + */ +public final class MultipartEntityUtil { + + /** + * Key to find the boundary in the Content-Type of the HTTP response. + */ + private static final String BOUNDARY_KEY = "boundary="; + + /** + * Private constructor. + */ + private MultipartEntityUtil() { + } + + /** + * Checks if the {@link HttpEntity} holds the multipart/byterange HTTP response. + * + * @param httpEntity + * {@link HttpEntity} that holds a response. + * @return True if it has the "multipart" marker in the response Content-Type header. + */ + public static boolean isMultipart(HttpEntity httpEntity) { + return httpEntity.getContentType().getValue().indexOf("multipart") != -1; + } + + /** + * Extracts the string that denotes the boundary of the multipart response from Content-Type + * header. + * + * @param httpEntity + * {@link HttpEntity} that holds a response. + * @return Boundary word, or null if the Content-Type header does not define it. + */ + public static String getBoundary(HttpEntity httpEntity) { + Header contentTypeHeader = httpEntity.getContentType(); + if (contentTypeHeader == null) { + return null; + } + StringTokenizer tokenizer = new StringTokenizer(contentTypeHeader.getValue(), ";"); + while (tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken().trim(); + int boundaryIndex = token.indexOf(BOUNDARY_KEY); + if (boundaryIndex != -1) { + String boundaryString = token.substring(boundaryIndex + BOUNDARY_KEY.length()); + return boundaryString; + } + } + return null; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/storage/util/UploadHttpEntityWrapper.java b/inspectIT/src/info/novatec/inspectit/rcp/storage/util/UploadHttpEntityWrapper.java new file mode 100644 index 000000000..2bb5acbf6 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/storage/util/UploadHttpEntityWrapper.java @@ -0,0 +1,46 @@ +package info.novatec.inspectit.rcp.storage.util; + +import info.novatec.inspectit.rcp.storage.http.TransferDataMonitor; +import info.novatec.inspectit.rcp.storage.http.TransferRateOutputStream; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.http.HttpEntity; +import org.apache.http.entity.HttpEntityWrapper; + +/** + * Wrapping entity to support upload speed monitoring. + * + * @author Ivan Senic + * + */ +public class UploadHttpEntityWrapper extends HttpEntityWrapper { + + /** + * {@link TransferDataMonitor} to pass to the {@link TransferRateOutputStream}. + */ + private TransferDataMonitor transferDataMonitor; + + /** + * Default constructor. + * + * @param wrapped + * Entity to be wrapped. + * @param transferDataMonitor + * {@link TransferDataMonitor} to pass to the {@link TransferRateOutputStream}. + */ + public UploadHttpEntityWrapper(HttpEntity wrapped, TransferDataMonitor transferDataMonitor) { + super(wrapped); + this.transferDataMonitor = transferDataMonitor; + } + + /** + * {@inheritDoc} + */ + public void writeTo(OutputStream outstream) throws IOException { + OutputStream targetStream = new TransferRateOutputStream(outstream, transferDataMonitor); + super.writeTo(targetStream); + }; + +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/tester/AgentTester.java b/inspectIT/src/info/novatec/inspectit/rcp/tester/AgentTester.java new file mode 100644 index 000000000..a18820558 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/tester/AgentTester.java @@ -0,0 +1,31 @@ +package info.novatec.inspectit.rcp.tester; + +import info.novatec.inspectit.communication.data.cmr.AgentStatusData.AgentConnection; +import info.novatec.inspectit.rcp.model.AgentLeaf; + +import org.eclipse.core.expressions.PropertyTester; + +/** + * Tester for {@link AgentLeaf}. + * + * @author Ivan Senic + * + */ +public class AgentTester extends PropertyTester { + + /** + * {@inheritDoc} + */ + @Override + public boolean test(Object receiver, String property, Object[] args, Object expectedValue) { + if (receiver instanceof AgentLeaf) { + AgentLeaf agentLeaf = (AgentLeaf) receiver; + + if ("canDelete".equals(property)) { + return null == agentLeaf.getAgentStatusData() || agentLeaf.getAgentStatusData().getAgentConnection() != AgentConnection.CONNECTED; + } + } + return false; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/tester/CanRefreshViewTester.java b/inspectIT/src/info/novatec/inspectit/rcp/tester/CanRefreshViewTester.java new file mode 100644 index 000000000..abb51b78b --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/tester/CanRefreshViewTester.java @@ -0,0 +1,23 @@ +package info.novatec.inspectit.rcp.tester; + +import info.novatec.inspectit.rcp.view.IRefreshableView; + +import org.eclipse.core.expressions.PropertyTester; + +/** + * Tester to check if {@link IRefreshableView} can be refreshed. + * + * @author Ivan Senic + * + */ +public class CanRefreshViewTester extends PropertyTester { + + /** + * {@inheritDoc} + */ + @Override + public boolean test(Object receiver, String property, Object[] args, Object expectedValue) { + return receiver instanceof IRefreshableView && ((IRefreshableView) receiver).canRefresh(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/tester/CmrOnlineStatusTester.java b/inspectIT/src/info/novatec/inspectit/rcp/tester/CmrOnlineStatusTester.java new file mode 100644 index 000000000..aec4a35d2 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/tester/CmrOnlineStatusTester.java @@ -0,0 +1,66 @@ +package info.novatec.inspectit.rcp.tester; + +import info.novatec.inspectit.rcp.provider.ICmrRepositoryAndAgentProvider; +import info.novatec.inspectit.rcp.provider.ICmrRepositoryProvider; +import info.novatec.inspectit.rcp.provider.IInputDefinitionProvider; +import info.novatec.inspectit.rcp.provider.IStorageDataProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; +import info.novatec.inspectit.storage.recording.RecordingState; + +import org.eclipse.core.expressions.PropertyTester; + +/** + * Tester for CMR Online Status. + * + * @author Ivan Senic + * + */ +public class CmrOnlineStatusTester extends PropertyTester { + + /** + * {@inheritDoc} + */ + @Override + public boolean test(Object receiver, String property, Object[] args, Object expectedValue) { + CmrRepositoryDefinition cmrRepositoryDefinition = null; + if (receiver instanceof ICmrRepositoryProvider) { + cmrRepositoryDefinition = ((ICmrRepositoryProvider) receiver).getCmrRepositoryDefinition(); + } else if (receiver instanceof ICmrRepositoryAndAgentProvider) { + cmrRepositoryDefinition = ((ICmrRepositoryAndAgentProvider) receiver).getCmrRepositoryDefinition(); + } else if (receiver instanceof IStorageDataProvider) { + cmrRepositoryDefinition = ((IStorageDataProvider) receiver).getCmrRepositoryDefinition(); + } else if (receiver instanceof IInputDefinitionProvider) { + RepositoryDefinition repository = ((IInputDefinitionProvider) receiver).getInputDefinition().getRepositoryDefinition(); + if (repository instanceof CmrRepositoryDefinition) { + cmrRepositoryDefinition = (CmrRepositoryDefinition) repository; + } else { + return false; + } + } else { + return false; + } + + if ("onlineStatus".equals(property)) { + if ("ONLINE".equals(expectedValue)) { + return cmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.ONLINE; + } else if ("OFFLINE".equals(expectedValue)) { + return cmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.OFFLINE; + } else if ("CHECKING".equals(expectedValue)) { + return cmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.CHECKING; + } + } else if ("recordingActive".equals(property)) { + if (expectedValue instanceof Boolean) { + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + boolean recordingActive = cmrRepositoryDefinition.getStorageService().getRecordingState() != RecordingState.OFF; + return ((Boolean) expectedValue).booleanValue() == recordingActive; + } else { + return false; + } + } + } + return false; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/tester/InputDefinitionTester.java b/inspectIT/src/info/novatec/inspectit/rcp/tester/InputDefinitionTester.java new file mode 100644 index 000000000..49e23253a --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/tester/InputDefinitionTester.java @@ -0,0 +1,36 @@ +package info.novatec.inspectit.rcp.tester; + +import info.novatec.inspectit.rcp.editor.inputdefinition.InputDefinition; +import info.novatec.inspectit.rcp.provider.IInputDefinitionProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.StorageRepositoryDefinition; + +import org.eclipse.core.expressions.PropertyTester; + +/** + * Tests the input definition for different properties. + * + * @author Ivan Senic + * + */ +public class InputDefinitionTester extends PropertyTester { + + /** + * {@inheritDoc} + */ + @Override + public boolean test(Object receiver, String property, Object[] args, Object expectedValue) { + if (receiver instanceof IInputDefinitionProvider) { + InputDefinition inputDefinition = ((IInputDefinitionProvider) receiver).getInputDefinition(); + if ("repositoryType".equals(property)) { + if ("cmrRepositoryDefinition".equals(expectedValue)) { + return inputDefinition.getRepositoryDefinition() instanceof CmrRepositoryDefinition; + } else if ("storageRepositoryDefinition".equals(expectedValue)) { + return inputDefinition.getRepositoryDefinition() instanceof StorageRepositoryDefinition; + } + } + } + return false; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/tester/StorageStateTester.java b/inspectIT/src/info/novatec/inspectit/rcp/tester/StorageStateTester.java new file mode 100644 index 000000000..256f9f351 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/tester/StorageStateTester.java @@ -0,0 +1,67 @@ +package info.novatec.inspectit.rcp.tester; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.provider.IStorageDataProvider; +import info.novatec.inspectit.rcp.storage.InspectITStorageManager; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageData.StorageState; + +import org.eclipse.core.expressions.PropertyTester; + +/** + * Testing the state of storage. Works if receiver is {@link StorageData} or + * {@link info.novatec.inspectit.rcp.model.storage.StorageLeaf}. + * + * @author Ivan Senic + * + */ +public class StorageStateTester extends PropertyTester { + + /** + * {@inheritDoc} + */ + @Override + public boolean test(Object receiver, String property, Object[] args, Object expectedValue) { + StorageData storageData = null; + if (receiver instanceof IStorageDataProvider) { + storageData = ((IStorageDataProvider) receiver).getStorageData(); + } else if (receiver instanceof StorageData) { + storageData = (StorageData) receiver; + } else { + return false; + } + + if ("storageState".equals(property)) { + if ("CREATED".equalsIgnoreCase((String) expectedValue) && storageData.getState() == StorageState.CREATED_NOT_OPENED) { + return true; + } else if ("OPENED".equalsIgnoreCase((String) expectedValue) && storageData.getState() == StorageState.OPENED) { + return true; + } else if ("RECORDING".equalsIgnoreCase((String) expectedValue) && storageData.getState() == StorageState.RECORDING) { + return true; + } else if ("CLOSED".equalsIgnoreCase((String) expectedValue) && storageData.getState() == StorageState.CLOSED) { + return true; + } + return false; + } + + if ("isStorageMounted".equals(property)) { + InspectITStorageManager storageManager = InspectIT.getDefault().getInspectITStorageManager(); + if (Boolean.TRUE.equals(expectedValue)) { + return storageManager.isStorageMounted(storageData); + } else if (Boolean.FALSE.equals(expectedValue)) { + return !storageManager.isStorageMounted(storageData); + } + } + + if ("isStorageDownloaded".equals(property)) { + InspectITStorageManager storageManager = InspectIT.getDefault().getInspectITStorageManager(); + if (Boolean.TRUE.equals(expectedValue)) { + return storageManager.isFullyDownloaded(storageData); + } else if (Boolean.FALSE.equals(expectedValue)) { + return !storageManager.isFullyDownloaded(storageData); + } + } + + return false; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/tester/TimerDataTester.java b/inspectIT/src/info/novatec/inspectit/rcp/tester/TimerDataTester.java new file mode 100644 index 000000000..22abea091 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/tester/TimerDataTester.java @@ -0,0 +1,34 @@ +package info.novatec.inspectit.rcp.tester; + +import info.novatec.inspectit.communication.data.TimerData; + +import org.eclipse.core.expressions.PropertyTester; + +/** + * Tester for charting possibilities of {@link TimerData}. + * + * @author Ivan Senic + * + */ +public class TimerDataTester extends PropertyTester { + + /** + * {@inheritDoc} + */ + @Override + public boolean test(Object receiver, String property, Object[] args, Object expectedValue) { + if (receiver instanceof TimerData) { + TimerData timerData = (TimerData) receiver; + + if ("canChart".equals(property)) { + if (expectedValue instanceof Boolean) { + return ((Boolean) expectedValue).booleanValue() == timerData.isCharting(); + } else { + return false; + } + } + } + return false; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/util/AccessibleArrowImage.java b/inspectIT/src/info/novatec/inspectit/rcp/util/AccessibleArrowImage.java new file mode 100644 index 000000000..634b83ecb --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/util/AccessibleArrowImage.java @@ -0,0 +1,162 @@ +package info.novatec.inspectit.rcp.util; + +import org.eclipse.jface.resource.CompositeImageDescriptor; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.forms.FormColors; + +/** + * An arrow image descriptor. The images color is related to the list fore- and background color. + * This makes the arrow visible even in high contrast mode. If ltr is true the arrow + * points to the right, otherwise it points to the left. + *

+ * IMPORTANT: The class is licensed under the Eclipse Public License v1.0 as it contains the + * code from the {@link org.eclipse.jdt.internal.ui.javaeditor.breadcrumb.BreadcrumbItem} class + * belonging to the Eclipse Rich Client Platform. EPL v1.0 license can be found here. + *

+ * Please relate to the LICENSEEXCEPTIONS.txt file for more information about license exceptions + * that apply regarding to InspectIT and Eclipse RCP and/or EPL Components. + */ +public class AccessibleArrowImage extends CompositeImageDescriptor { + + /** + * Arrow size. + */ + private static final int ARROW_SIZE = 5; + + /** + * Left to right arrow boolean. + */ + private final boolean fLTR; + + /** + * Default constructor. + * + * @param ltr + * Left to right arrow. + */ + public AccessibleArrowImage(boolean ltr) { + fLTR = ltr; + } + + /** + * Draw the composite images. + *

+ * Subclasses must implement this framework method to paint images within the given bounds using + * one or more calls to the drawImage framework method. + *

+ * + * @param width + * the width + * @param height + * the height + * @see org.eclipse.jface.resource.CompositeImageDescriptor#drawCompositeImage(int, int) + */ + protected void drawCompositeImage(int width, int height) { + Display display = Display.getDefault(); + + Image image = new Image(display, ARROW_SIZE, ARROW_SIZE * 2); + + GC gc = new GC(image); + + Color triangle = createColor(SWT.COLOR_LIST_FOREGROUND, SWT.COLOR_LIST_BACKGROUND, 20, display); + Color aliasing = createColor(SWT.COLOR_LIST_FOREGROUND, SWT.COLOR_LIST_BACKGROUND, 30, display); + gc.setBackground(triangle); + + if (fLTR) { + gc.fillPolygon(new int[] { mirror(0), 0, mirror(ARROW_SIZE), ARROW_SIZE, mirror(0), ARROW_SIZE * 2 }); + } else { + gc.fillPolygon(new int[] { ARROW_SIZE, 0, 0, ARROW_SIZE, ARROW_SIZE, ARROW_SIZE * 2 }); + } + + gc.setForeground(aliasing); + gc.drawLine(mirror(0), 1, mirror(ARROW_SIZE - 1), ARROW_SIZE); + gc.drawLine(mirror(ARROW_SIZE - 1), ARROW_SIZE, mirror(0), ARROW_SIZE * 2 - 1); + + gc.dispose(); + triangle.dispose(); + aliasing.dispose(); + + ImageData imageData = image.getImageData(); + for (int y = 1; y < ARROW_SIZE; y++) { + for (int x = 0; x < y; x++) { + imageData.setAlpha(mirror(x), y, 255); + } + } + for (int y = 0; y < ARROW_SIZE; y++) { + for (int x = 0; x <= y; x++) { + imageData.setAlpha(mirror(x), ARROW_SIZE * 2 - y - 1, 255); + } + } + + int offset = 0; + if (!fLTR) { + offset = -1; + } + drawImage(imageData, width / 2 - ARROW_SIZE / 2 + offset, height / 2 - ARROW_SIZE - 1); + + image.dispose(); + } + + /** + * Returns correct number of pixels depending on the arrow orientation. If arrow is set to be + * from left to right original parameter values i returned. if not then the mirrored value is + * returned. + * + * @param x + * Pixels. + * @return Returns correct number of pixels depending on the arrow orientation. If arrow is set + * to be from left to right original parameter values i returned. if not then the + * mirrored value is returned. + */ + private int mirror(int x) { + if (fLTR) { + return x; + } + + return ARROW_SIZE - x - 1; + } + + /** + * Return the size of this composite image. + *

+ * Subclasses must implement this framework method. + *

+ * + * @return the x and y size of the image expressed as a point object + */ + protected Point getSize() { + return new Point(10, 16); + } + + /** + * Blends two colors with the given ration. The colors are represented by int values as colors + * as defined in the {@link SWT} class. + * + * @param color1 + * First color. + * @param color2 + * Second color. + * @param ratio + * Percentage of the first color in the blend (0-100). + * @param display + * {@link Display} + * @return New color. + * @see FormColors#blend(RGB, RGB, int) + */ + private Color createColor(int color1, int color2, int ratio, Display display) { + RGB rgb1 = display.getSystemColor(color1).getRGB(); + RGB rgb2 = display.getSystemColor(color2).getRGB(); + + RGB blend = FormColors.blend(rgb2, rgb1, ratio); + + return new Color(display, blend); + } +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/util/ElementOccurrenceCount.java b/inspectIT/src/info/novatec/inspectit/rcp/util/ElementOccurrenceCount.java new file mode 100644 index 000000000..9e541d089 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/util/ElementOccurrenceCount.java @@ -0,0 +1,96 @@ +package info.novatec.inspectit.rcp.util; + +/** + * Simple POJO for occurrence counting in the invocation sequence data. + * + * @author Ivan Senic + * + */ +public class ElementOccurrenceCount { + + /** + * Number of visible occurrences. + */ + private int visibleOccurrences; + + /** + * Number of filtered occurrences. + */ + private int filteredOccurrences; + + /** + * Reference to the empty element that can used for returning in exceptional cases. + */ + private static ElementOccurrenceCount empty = new ElementOccurrenceCount(); + + /** + * @return the visibleOccurrences + */ + public int getVisibleOccurrences() { + return visibleOccurrences; + } + + /** + * @param visibleOccurrences + * the visibleOccurrences to set + */ + public void setVisibleOccurrences(int visibleOccurrences) { + if (this != empty) { + this.visibleOccurrences = visibleOccurrences; + } + } + + /** + * Increases the visible occurrences count. + */ + public void increaseVisibleOccurrences() { + if (this != empty) { + visibleOccurrences++; + } + } + + /** + * @return the filteredOccurrences + */ + public int getFilteredOccurrences() { + return filteredOccurrences; + } + + /** + * @param filteredOccurrences + * the filteredOccurrences to set + */ + public void setFilteredOccurrences(int filteredOccurrences) { + if (this != empty) { + this.filteredOccurrences = filteredOccurrences; + } + } + + /** + * Increases the filtered occurrences count. + */ + public void increaseFilteredOccurrences() { + if (this != empty) { + filteredOccurrences++; + } + } + + /** + * Total amount of occurrences. + * + * @return Total amount of occurrences. + */ + public int getTotalOccurrences() { + return visibleOccurrences + filteredOccurrences; + } + + /** + * Returns the empty element. The returned element content can not be changed. + * + * @return Returns the empty element. + */ + public static ElementOccurrenceCount emptyElement() { + return empty; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/util/ListenerList.java b/inspectIT/src/info/novatec/inspectit/rcp/util/ListenerList.java new file mode 100644 index 000000000..48eb84df1 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/util/ListenerList.java @@ -0,0 +1,259 @@ +package info.novatec.inspectit.rcp.util; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * This class is a thread safe list that is designed for storing lists of listeners. The + * implementation is optimized for minimal memory footprint, frequent reads and infrequent writes. + * Modification of the list is synchronized and relatively expensive, while accessing the listeners + * is very fast. Readers are given access to the underlying array data structure for reading, with + * the trust that they will not modify the underlying array. + *

+ * A listener list handles the same listener being added multiple times, and + * tolerates removal of listeners that are the same as other listeners in the list. For this + * purpose, listeners can be compared with each other using either equality or identity, as + * specified in the list constructor. + *

+ * IMPORTANT: The class is licensed under the Eclipse Public License v1.0 as it includes the + * code from the {@link org.eclipse.core.runtime.ListenerList} class belonging to the Eclipse Rich + * Client Platform. EPL v1.0 license can be found here. + *

+ * Please relate to the LICENSEEXCEPTIONS.txt file for more information about license exceptions + * that apply regarding to InspectIT and Eclipse RCP and/or EPL Components. + * + * @param + * Generic parameter that defines the type of this listener list. + */ +@SuppressWarnings("unchecked") +public class ListenerList implements Iterable { + + /** + * The empty array singleton instance. + */ + private final E[] emptyArray = (E[]) new Object[0]; + + /** + * The available comparison modes. + * + * @author Patrice Bouillet + * + */ + public enum Mode { + /** + * Mode constant (value 0) indicating that listeners should be considered the same if they are equal. + */ + EQUALITY, + + /** + * Mode constant (value 1) indicating that listeners should be considered the same if they are identical. + */ + IDENTITY; + } + + /** + * Indicates the comparison mode used to determine if two listeners are equivalent. + */ + private final boolean identity; + + /** + * The list of listeners. Initially empty but initialized to an array of size capacity the first + * time a listener is added. Maintains invariant: listeners != null. + */ + private volatile E[] listeners = emptyArray; + + /** + * Creates a listener list in which listeners are compared using equality. + */ + public ListenerList() { + this(Mode.EQUALITY); + } + + /** + * Creates a listener list using the provided comparison mode. + * + * @param mode + * The mode used to determine if listeners are the same. + */ + public ListenerList(Mode mode) { + this.identity = Mode.IDENTITY.equals(mode); + } + + /** + * Adds a listener to this list. This method has no effect if the same + * listener is already registered. + * + * @param listener + * the non-null listener to add + */ + public synchronized void add(E listener) { + // This method is synchronized to protect against multiple threads + // adding or removing listeners concurrently. This does not block + // concurrent readers. + if (listener == null) { + throw new IllegalArgumentException(); + } + + // check for duplicates + final int oldSize = listeners.length; + for (int i = 0; i < oldSize; ++i) { + E listener2 = listeners[i]; + if (identity ? listener == listener2 : listener.equals(listener2)) { // NOPMD + return; + } + } + + // Thread safety: create new array to avoid affecting concurrent readers + E[] newListeners = (E[]) new Object[oldSize + 1]; + System.arraycopy(listeners, 0, newListeners, 0, oldSize); + newListeners[oldSize] = listener; + + // atomic assignment + this.listeners = newListeners; + } + + /** + * Returns an array containing all the registered listeners. The resulting array is unaffected + * by subsequent adds or removes. If there are no listeners registered, the result is an empty + * array. Use this method when notifying listeners, so that any modifications to the listener + * list during the notification will have no effect on the notification itself. + *

+ * Note: Callers of this method must not modify the returned array. + * + * @return the list of registered listeners + */ + public E[] getListeners() { + return listeners; + } + + /** + * Returns whether this listener list is empty. + * + * @return true if there are no registered listeners, and false + * otherwise + */ + public boolean isEmpty() { + return listeners.length == 0; + } + + /** + * Removes a listener from this list. Has no effect if the same listener was + * not already registered. + * + * @param listener + * the non-null listener to remove + */ + public synchronized void remove(E listener) { + // This method is synchronized to protect against multiple threads + // adding or removing listeners concurrently. This does not block + // concurrent readers. + if (listener == null) { + throw new IllegalArgumentException(); + } + + int oldSize = listeners.length; + for (int i = 0; i < oldSize; ++i) { + E listener2 = listeners[i]; + if (identity ? listener == listener2 : listener.equals(listener2)) { // NOPMD + if (oldSize == 1) { + listeners = emptyArray; + } else { + // Thread safety: create new array to avoid affecting + // concurrent readers + E[] newListeners = (E[]) new Object[oldSize - 1]; + System.arraycopy(listeners, 0, newListeners, 0, i); + System.arraycopy(listeners, i + 1, newListeners, i, oldSize - i - 1); + // atomic assignment to field + this.listeners = newListeners; + } + return; + } + } + } + + /** + * Returns the number of registered listeners. + * + * @return the number of registered listeners + */ + public int size() { + return listeners.length; + } + + /** + * Removes all listeners from this list. + */ + public synchronized void clear() { + listeners = emptyArray; + } + + /** + * {@inheritDoc} + */ + @Override + public Iterator iterator() { + return new Itr(listeners); + } + + /** + * The iterator implementation for this listener list. + * + * @author Patrice Bouillet + * + */ + private class Itr implements Iterator { + + /** + * Index of element to be returned by subsequent call to next. + */ + private int cursor = 0; + + /** + * The listeners for this iterator. + */ + private E[] listeners; + + /** + * The reference to the generic listeners array is passed here because in the meantime, + * while iterating over this list, some new objects could be added which is NOT reflected. + * + * @param listeners + * The listeners. + */ + public Itr(E[] listeners) { + this.listeners = listeners; + } + + /** + * {@inheritDoc} + */ + public boolean hasNext() { + return cursor != listeners.length; + } + + /** + * {@inheritDoc} + */ + public E next() { + try { + E next = listeners[cursor]; + cursor++; + return next; + } catch (IndexOutOfBoundsException e) { + throw new NoSuchElementException(); // NOPMD + } + } + + /** + * {@inheritDoc} + */ + public void remove() { + throw new UnsupportedOperationException(); + } + + } + +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/util/OccurrenceFinderFactory.java b/inspectIT/src/info/novatec/inspectit/rcp/util/OccurrenceFinderFactory.java new file mode 100644 index 000000000..b43dfb35a --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/util/OccurrenceFinderFactory.java @@ -0,0 +1,579 @@ +package info.novatec.inspectit.rcp.util; + +import info.novatec.inspectit.communication.MethodSensorData; +import info.novatec.inspectit.communication.data.AggregatedTimerData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.mutable.MutableInt; +import org.eclipse.jface.viewers.ViewerFilter; + +/** + * Factory for finding the occurrence of elements in the {@link InvocationSequenceData} based on the + * element type. + * + * @author Ivan Senic + * + */ +public final class OccurrenceFinderFactory { + + /** + * String used buy templates to denote a String that will not be checked when matching. + */ + private static final String TEMPLATE_STRING = "TEMPLATE_STRING"; + + /** + * Map used by templates to denote a Map that will not be checked when matching. Currently not + * in use, but in future it might be needed. + */ + @SuppressWarnings("unused") + private static final Map TEMPLATE_MAP = new HashMap(0); + + /** + * List used buy templates to denote a List that will not be checked when matching. + */ + private static final List TEMPLATE_LIST = new ArrayList(0); + + /** + * Set used buy templates to denote a Set that will not be checked when matching. Currently not + * in use, but in future it might be needed. + */ + @SuppressWarnings("unused") + private static final Set TEMPLATE_SET = new HashSet(0); + + /** + * Private constructor because of the factory. + */ + private OccurrenceFinderFactory() { + } + + /** + * Occurrence finder for {@link TimerData}. + */ + private static TimerOccurrenceFinder timerOccurrenceFinder = new TimerOccurrenceFinder(); + + /** + * Occurrence finder for {@link SqlStatementData}. + */ + private static SqlOccurrenceFinder sqlOccurrenceFinder = new SqlOccurrenceFinder(); + + /** + * Occurrence finder for {@link ExceptionSensorData}. + */ + private static ExceptionOccurrenceFinder exceptionOccurrenceFinder = new ExceptionOccurrenceFinder(); + + /** + * Occurrence finder for {@link InvocationSequenceData}. + */ + private static InvocationOccurenceFinder invocationOccurenceFinder = new InvocationOccurenceFinder(); + + /** + * Counts number of occurrences of the element in the given invocation. + * + * @param invocation + * Invocation to search in. + * @param element + * Wanted element. + * @param filters + * Array of filters that each found occurrence has to pass. + * @return Number of occurrences found and filtered. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static ElementOccurrenceCount getOccurrenceCount(InvocationSequenceData invocation, Object element, ViewerFilter[] filters) { + OccurrenceFinder finder = getOccurrenceFinder(element); + return finder.getOccurrenceCount(invocation, element, filters, null); + } + + /** + * Returns the {@link InvocationSequenceData} that holds the proper occurrence of the wanted + * element if it exists. + * + * @param invocation + * Invocation to search in. + * @param element + * Wanted element. + * @param occurrence + * Wanted occurrence. + * @param filters + * Array of filters that each found occurrence has to pass. + * @return Returns the {@link InvocationSequenceData} that holds the proper occurrence of the + * wanted element if it exists. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static InvocationSequenceData getOccurrence(InvocationSequenceData invocation, Object element, int occurrence, ViewerFilter[] filters) { + OccurrenceFinder finder = getOccurrenceFinder(element); + return finder.getOccurrence(invocation, element, new MutableInt(occurrence), filters); + } + + /** + * Returns empty template. + * + * @param + * Type of template. + * @param element + * For element. + * @return Template to be used. + */ + @SuppressWarnings({ "unchecked" }) + public static E getEmptyTemplate(E element) { + OccurrenceFinder finder = getOccurrenceFinder(element); + return finder.getEmptyTemplate(); + } + + /** + * Returns the proper {@link OccurrenceFinder} for the given element, based on elements class. + * + * @param element + * Element. + * @return {@link OccurrenceFinder} or null if the finder does not exists for the given object + * type. + */ + @SuppressWarnings("rawtypes") + private static OccurrenceFinder getOccurrenceFinder(Object element) { + if (SqlStatementData.class.isAssignableFrom(element.getClass())) { + return sqlOccurrenceFinder; + } else if (element.getClass().equals(TimerData.class) || element.getClass().equals(AggregatedTimerData.class)) { + return timerOccurrenceFinder; + } else if (ExceptionSensorData.class.isAssignableFrom(element.getClass())) { + return exceptionOccurrenceFinder; + } else if (InvocationSequenceData.class.isAssignableFrom(element.getClass())) { + return invocationOccurenceFinder; + } + RuntimeException exception = new RuntimeException("Occurrence finder factory was not able to supply the correct occurrence finder for the object of class " + element.getClass().getName() + + "."); + InspectIT.getDefault().createErrorDialog("Exception thrown during locating of stepping object.", exception, -1); + throw exception; + } + + /** + * Abstract class that holds the shared functionality of all occurrence finders. + * + * @author Ivan Senic + * + * @param + * Type of the element finder can locate. + */ + private abstract static class OccurrenceFinder { + + /** + * Returns the number of children objects in invocation sequence that have the wanted + * template object defined. This method is recursive, and traverse the whole invocation + * tree. + * + * @param invocationData + * Top parent invocation sequence. + * @param template + * Template data to search for. + * @param filters + * Active filters of the tree viewer. + * @param elementOccurrence + * Element occurrence count. + * @return Number of children in invocation that have template data set. + */ + public ElementOccurrenceCount getOccurrenceCount(InvocationSequenceData invocationData, E template, ViewerFilter[] filters, ElementOccurrenceCount elementOccurrence) { + if (!getConcreteClass().isAssignableFrom(template.getClass())) { + return null; + } + ElementOccurrenceCount occurrenceCount; + if (null == elementOccurrence) { + occurrenceCount = new ElementOccurrenceCount(); + } else { + occurrenceCount = elementOccurrence; + } + + boolean found = occurrenceFound(invocationData, template); + if (found && filtersPassed(invocationData, filters)) { + occurrenceCount.increaseVisibleOccurrences(); + } else if (found) { + occurrenceCount.increaseFilteredOccurrences(); + } + + if (null != invocationData.getNestedSequences()) { + for (InvocationSequenceData child : (List) invocationData.getNestedSequences()) { + getOccurrenceCount(child, template, filters, occurrenceCount); + } + } + return occurrenceCount; + } + + /** + * Get empty template. + * + * @return Empty template. + */ + public abstract E getEmptyTemplate(); + + /** + * Returns the {@link InvocationSequenceData} object that has the wanted template data + * object defined. The wanted occurrence of E object is defined via {@link #occurrencesLeft} + * , before this method is called. This method is recursive, and stops traversing the + * invocation sequence tree as soon the wanted element is found. + * + * @param invocationData + * Top parent invocation sequence. + * @param template + * Template data. + * @param occurrencesLeft + * Occurrence to search for. + * @param filters + * Active filters of the tree viewer. + * @return Invocation sequence that has the Exception data set in Exceptions list and is + * same as template data. + */ + public InvocationSequenceData getOccurrence(InvocationSequenceData invocationData, E template, MutableInt occurrencesLeft, ViewerFilter[] filters) { + if (!getConcreteClass().isAssignableFrom(template.getClass())) { + return null; + } + if (occurrenceFound(invocationData, template) && filtersPassed(invocationData, filters)) { + occurrencesLeft.decrement(); + if (occurrencesLeft.intValue() == 0) { + return invocationData; + } + } + if (null != invocationData.getNestedSequences()) { + for (InvocationSequenceData child : (List) invocationData.getNestedSequences()) { + InvocationSequenceData foundData = getOccurrence(child, template, occurrencesLeft, filters); + if (null != foundData) { + return foundData; + } + } + } + return null; + } + + /** + * Returns if the template objects is found in the invocation data. + * + * @param invocationData + * Invocation data to look in. + * @param template + * Template object. + * @return Return depends on the implementing classes. + */ + public abstract boolean occurrenceFound(InvocationSequenceData invocationData, E template); + + /** + * Compares if the template equals the element from the view of the finder. + * + * @param template + * Template. + * @param element + * Element. + * @return True if templates are equal. + */ + public abstract boolean doesTemplateEqualsElement(E template, E element); + + /** + * Returns the concrete class that finder is working with. + * + * @return Returns the concrete class that finder is working with. + */ + public abstract Class getConcreteClass(); + + /** + * Returns if the invocation data object is passing all given filters. + * + * @param invocationData + * Invocation data. + * @param filters + * Array of filters. + * @return True if all filters are passed, or filters array is null or empty. + */ + private boolean filtersPassed(InvocationSequenceData invocationData, ViewerFilter[] filters) { + boolean passed = true; + if (null != filters) { + for (ViewerFilter filter : filters) { + if (!filter.select(null, invocationData.getParentSequence(), invocationData)) { + passed = false; + break; + } + } + } + return passed; + } + + } + + /** + * Occurrence finder for {@link ExceptionSensorData}. + * + * @author Ivan Senic + * + */ + private static class ExceptionOccurrenceFinder extends OccurrenceFinder { + + /** + * {@inheritDoc} + */ + @Override + public boolean occurrenceFound(InvocationSequenceData invocationData, ExceptionSensorData template) { + if (invocationData.getExceptionSensorDataObjects() != null) { + for (ExceptionSensorData exData : (List) invocationData.getExceptionSensorDataObjects()) { + return doesTemplateEqualsElement(template, exData); + } + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public Class getConcreteClass() { + return ExceptionSensorData.class; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean doesTemplateEqualsElement(ExceptionSensorData template, ExceptionSensorData element) { + if (0 != template.getId()) { + if (template.getId() != element.getId()) { + return false; + } + } + if (TEMPLATE_STRING != template.getCause()) { + if (!ObjectUtils.equals(template.getCause(), element.getCause())) { + return false; + } + } + if (TEMPLATE_STRING != template.getErrorMessage()) { + if (!ObjectUtils.equals(template.getErrorMessage(), element.getErrorMessage())) { + return false; + } + } + if (TEMPLATE_STRING != template.getStackTrace()) { + // allow also parts of stack traces + if (!StringUtils.contains(element.getStackTrace(), template.getStackTrace())) { + return false; + } + } + if (TEMPLATE_STRING != template.getThrowableType()) { + if (!ObjectUtils.equals(template.getThrowableType(), element.getThrowableType())) { + return false; + } + } + if (0 != template.getThrowableIdentityHashCode()) { + if (template.getThrowableIdentityHashCode() != element.getThrowableIdentityHashCode()) { + return false; + } + } + if (null != template.getExceptionEvent()) { + if (!ObjectUtils.equals(template.getExceptionEvent(), element.getExceptionEvent())) { + return false; + } + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public ExceptionSensorData getEmptyTemplate() { + ExceptionSensorData exceptionSensorData = new ExceptionSensorData(); + exceptionSensorData.setCause(TEMPLATE_STRING); + exceptionSensorData.setErrorMessage(TEMPLATE_STRING); + exceptionSensorData.setStackTrace(TEMPLATE_STRING); + exceptionSensorData.setThrowableType(TEMPLATE_STRING); + exceptionSensorData.setThrowableIdentityHashCode(0); + exceptionSensorData.setExceptionEvent(null); + return exceptionSensorData; + } + + } + + /** + * Occurrence finder for {@link SqlStatementData}. + * + * @author Ivan Senic + * + */ + private static class SqlOccurrenceFinder extends OccurrenceFinder { + + /** + * {@inheritDoc} + */ + @Override + public boolean occurrenceFound(InvocationSequenceData invocationData, SqlStatementData template) { + if (invocationData.getSqlStatementData() != null) { + return doesTemplateEqualsElement(template, invocationData.getSqlStatementData()); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public Class getConcreteClass() { + return SqlStatementData.class; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean doesTemplateEqualsElement(SqlStatementData template, SqlStatementData element) { + if (0 != template.getId()) { + if (template.getId() != element.getId()) { + return false; + } + } + if (TEMPLATE_STRING != template.getSql()) { + if (!ObjectUtils.equals(template.getSql(), element.getSql())) { + return false; + } + } + if (TEMPLATE_LIST != template.getParameterValues()) { + if (!ObjectUtils.equals(template.getParameterValues(), element.getParameterValues())) { + return false; + } + } + if (0 != template.getMethodIdent()) { + if (template.getMethodIdent() != element.getMethodIdent()) { + return false; + } + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public SqlStatementData getEmptyTemplate() { + SqlStatementData sqlStatementData = new SqlStatementData(); + sqlStatementData.setSql(TEMPLATE_STRING); + sqlStatementData.setParameterValues(TEMPLATE_LIST); + sqlStatementData.setMethodIdent(0); + return sqlStatementData; + } + + } + + /** + * Occurrence finder for {@link TimerData}. + * + * @author Ivan Senic + * + */ + private static class TimerOccurrenceFinder extends OccurrenceFinder { + + /** + * {@inheritDoc} + */ + @Override + public boolean occurrenceFound(InvocationSequenceData invocationData, MethodSensorData template) { + if (invocationData.getTimerData() != null) { + return doesTemplateEqualsElement(template, invocationData.getTimerData()); + } else { + return doesTemplateEqualsElement(template, invocationData); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Class getConcreteClass() { + return TimerData.class; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean doesTemplateEqualsElement(MethodSensorData template, MethodSensorData element) { + if (0 != template.getId()) { + if (template.getId() != element.getId()) { + return false; + } + } + if (0 != template.getMethodIdent()) { + if (template.getMethodIdent() != element.getMethodIdent()) { + return false; + } + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public MethodSensorData getEmptyTemplate() { + TimerData timerData = new TimerData(); + timerData.setMethodIdent(0); + return timerData; + } + + } + + /** + * Occurrence finder for {@link InvocationSequenceData}. + * + * @author Ivan Senic + * + */ + private static class InvocationOccurenceFinder extends OccurrenceFinder { + + /** + * {@inheritDoc} + */ + @Override + public InvocationSequenceData getEmptyTemplate() { + InvocationSequenceData invocation = new InvocationSequenceData(); + invocation.setMethodIdent(0); + return invocation; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean occurrenceFound(InvocationSequenceData invocationData, InvocationSequenceData template) { + return doesTemplateEqualsElement(template, invocationData); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean doesTemplateEqualsElement(InvocationSequenceData template, InvocationSequenceData element) { + if (0 != template.getId()) { + if (template.getId() != element.getId()) { + return false; + } + } + if (0 != template.getMethodIdent()) { + if (template.getMethodIdent() != element.getMethodIdent()) { + return false; + } + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public Class getConcreteClass() { + return InvocationSequenceData.class; + } + + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/util/SelectionProviderAdapter.java b/inspectIT/src/info/novatec/inspectit/rcp/util/SelectionProviderAdapter.java new file mode 100644 index 000000000..f45b118e4 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/util/SelectionProviderAdapter.java @@ -0,0 +1,74 @@ +package info.novatec.inspectit.rcp.util; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.runtime.SafeRunner; +import org.eclipse.jface.util.SafeRunnable; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; + +/** + * Simple class that can publish selection of the view to the site. + *

+ * IMPORTANT: The class is licensed under the Eclipse Public License v1.0 as it includes the + * code from the {@link org.eclipse.ui.internal.part.SelectionProviderAdapter} class belonging to + * the Eclipse Rich Client Platform. EPL v1.0 license can be found here. + *

+ * Please relate to the LICENSEEXCEPTIONS.txt file for more information about license exceptions + * that apply regarding to InspectIT and Eclipse RCP and/or EPL Components. + * + */ +public class SelectionProviderAdapter implements ISelectionProvider { + + /** + * List of listeners. + */ + private List listeners = new ArrayList(); + + /** + * Current selection. + */ + private ISelection theSelection = StructuredSelection.EMPTY; + + /** + * {@inheritDoc} + */ + public void addSelectionChangedListener(ISelectionChangedListener listener) { + listeners.add(listener); + } + + /** + * {@inheritDoc} + */ + public ISelection getSelection() { + return theSelection; + } + + /** + * {@inheritDoc} + */ + public void removeSelectionChangedListener(ISelectionChangedListener listener) { + listeners.remove(listener); + } + + /** + * {@inheritDoc} + */ + public void setSelection(ISelection selection) { + theSelection = selection; + final SelectionChangedEvent e = new SelectionChangedEvent(this, selection); + for (final ISelectionChangedListener listener : listeners) { + SafeRunner.run(new SafeRunnable() { + public void run() { + listener.selectionChanged(e); + } + }); + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/util/data/DatabaseInfoHelper.java b/inspectIT/src/info/novatec/inspectit/rcp/util/data/DatabaseInfoHelper.java new file mode 100644 index 000000000..222a4adbe --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/util/data/DatabaseInfoHelper.java @@ -0,0 +1,129 @@ +package info.novatec.inspectit.rcp.util.data; + +import info.novatec.inspectit.communication.data.SqlStatementData; + +import org.apache.commons.lang.StringUtils; + +/** + * Helper class for displaying the database info. + * + * @author Ivan Senic + * + */ +public class DatabaseInfoHelper { + + /** + * The URL that the connection uses. + */ + private String databaseUrl; + + /** + * The name of the database product. + */ + private String databaseProductName; + + /** + * The version of the database product. + */ + private String databaseProductVersion; + + /** + * Default constructor. + * + * @param sqlStatementData + * {@link SqlStatementData} to copy the database information from. + */ + public DatabaseInfoHelper(SqlStatementData sqlStatementData) { + this.databaseProductName = sqlStatementData.getDatabaseProductName(); + this.databaseProductVersion = sqlStatementData.getDatabaseProductVersion(); + this.databaseUrl = sqlStatementData.getDatabaseUrl(); + } + + /** + * Gets {@link #databaseUrl}. + * + * @return {@link #databaseUrl} + */ + public String getDatabaseUrl() { + if (StringUtils.isNotBlank(databaseUrl)) { + return databaseUrl; + } else { + return "Unknown"; + } + } + + /** + * @return Returns long description text or null if {@link #databaseProductName} is + * not specified. + */ + public String getLongText() { + if (StringUtils.isNotEmpty(databaseProductName)) { + StringBuilder stringBuilder = new StringBuilder(databaseProductName); + if (StringUtils.isNotEmpty(databaseProductVersion)) { + stringBuilder.append(" v. "); + stringBuilder.append(databaseProductVersion); + } + if (StringUtils.isNotEmpty(databaseUrl)) { + stringBuilder.append(" (URL: "); + stringBuilder.append(databaseUrl); + stringBuilder.append(')'); + } + return stringBuilder.toString(); + } else { + return null; + } + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((databaseProductName == null) ? 0 : databaseProductName.hashCode()); + result = prime * result + ((databaseProductVersion == null) ? 0 : databaseProductVersion.hashCode()); + result = prime * result + ((databaseUrl == null) ? 0 : databaseUrl.hashCode()); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + DatabaseInfoHelper other = (DatabaseInfoHelper) obj; + if (databaseProductName == null) { + if (other.databaseProductName != null) { + return false; + } + } else if (!databaseProductName.equals(other.databaseProductName)) { + return false; + } + if (databaseProductVersion == null) { + if (other.databaseProductVersion != null) { + return false; + } + } else if (!databaseProductVersion.equals(other.databaseProductVersion)) { + return false; + } + if (databaseUrl == null) { + if (other.databaseUrl != null) { + return false; + } + } else if (!databaseUrl.equals(other.databaseUrl)) { + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/util/data/RegExAggregatedHttpTimerData.java b/inspectIT/src/info/novatec/inspectit/rcp/util/data/RegExAggregatedHttpTimerData.java new file mode 100644 index 000000000..3568d3401 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/util/data/RegExAggregatedHttpTimerData.java @@ -0,0 +1,127 @@ +package info.novatec.inspectit.rcp.util.data; + +import info.novatec.inspectit.cmr.model.MethodSensorTypeIdent; +import info.novatec.inspectit.cmr.model.MethodSensorTypeIdentHelper; +import info.novatec.inspectit.communication.data.AggregatedHttpTimerData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.HttpTimerDataHelper; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.base.Objects; + +/** + * Simple extension of {@link AggregatedHttpTimerData}. + * + * @author Ivan Senic + * + */ +public class RegExAggregatedHttpTimerData extends AggregatedHttpTimerData { + + /** + * Generated UID. + */ + private static final long serialVersionUID = -7733383915089404688L; + + /** + * Transformed URI. + */ + private String transformedUri; + + /** + * List of aggregated HTTP datas. + */ + private List aggregatedDataList = new ArrayList<>(); + + /** + * No-arg constructor. + */ + public RegExAggregatedHttpTimerData() { + } + + /** + * Default constructor. + * + * @param transformedUri + * Transformed URI. + */ + public RegExAggregatedHttpTimerData(String transformedUri) { + this.transformedUri = transformedUri; + } + + /** + * Returns transformed URI. + * + * @param object + * {@link HttpTimerData} + * @param httpSensorTypeIdent + * Sensor that holds the regEx expression and the template. + * @return Transformed URI or URI if regular expression is not provided or can not be compiled. + */ + public static String getTransformedUri(HttpTimerData object, MethodSensorTypeIdent httpSensorTypeIdent) { + try { + if (null != httpSensorTypeIdent && null != MethodSensorTypeIdentHelper.getRegEx(httpSensorTypeIdent)) { + return HttpTimerDataHelper.getTransformedUri(object, MethodSensorTypeIdentHelper.getRegEx(httpSensorTypeIdent), MethodSensorTypeIdentHelper.getRegExTemplate(httpSensorTypeIdent)); + } else { + return object.getUri(); + } + } catch (IllegalArgumentException e) { + return object.getUri(); + } + } + + /** + * Gets {@link #transformedUri}. + * + * @return {@link #transformedUri} + */ + public String getTransformedUri() { + return transformedUri; + } + + /** + * Sets {@link #transformedUri}. + * + * @param transformedUri + * New value for {@link #transformedUri} + */ + public void setTransformedUri(String transformedUri) { + this.transformedUri = transformedUri; + } + + /** + * Gets {@link #aggregatedDataList}. + * + * @return {@link #aggregatedDataList} + */ + public List getAggregatedDataList() { + return aggregatedDataList; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(transformedUri, aggregatedDataList); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + RegExAggregatedHttpTimerData that = (RegExAggregatedHttpTimerData) object; + return Objects.equal(this.transformedUri, that.transformedUri) && Objects.equal(this.aggregatedDataList, that.aggregatedDataList); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/view/IRefreshableView.java b/inspectIT/src/info/novatec/inspectit/rcp/view/IRefreshableView.java new file mode 100644 index 000000000..42424d4d4 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/view/IRefreshableView.java @@ -0,0 +1,22 @@ +package info.novatec.inspectit.rcp.view; + +/** + * Interface for all view that are refreshable. + * + * @author Ivan Senic + * + */ +public interface IRefreshableView { + + /** + * Perform view refresh. + */ + void refresh(); + + /** + * Defines if the view can be refreshed. + * + * @return True if the view can be refreshed at the given point of call. + */ + boolean canRefresh(); +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/view/impl/DataExplorerView.java b/inspectIT/src/info/novatec/inspectit/rcp/view/impl/DataExplorerView.java new file mode 100644 index 000000000..2c26c86aa --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/view/impl/DataExplorerView.java @@ -0,0 +1,953 @@ +package info.novatec.inspectit.rcp.view.impl; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.cmr.service.exception.ServiceException; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.tree.DeferredTreeViewer; +import info.novatec.inspectit.rcp.formatter.ImageFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.model.TreeModelManager; +import info.novatec.inspectit.rcp.preferences.PreferencesConstants; +import info.novatec.inspectit.rcp.preferences.PreferencesUtils; +import info.novatec.inspectit.rcp.repository.CmrRepositoryChangeListener; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.repository.CmrRepositoryManager.UpdateRepositoryJob; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; +import info.novatec.inspectit.rcp.repository.StorageRepositoryDefinition; +import info.novatec.inspectit.rcp.storage.listener.StorageChangeListener; +import info.novatec.inspectit.rcp.util.SelectionProviderAdapter; +import info.novatec.inspectit.rcp.view.IRefreshableView; +import info.novatec.inspectit.rcp.view.listener.TreeViewDoubleClickListener; +import info.novatec.inspectit.rcp.view.tree.TreeContentProvider; +import info.novatec.inspectit.rcp.view.tree.TreeLabelProvider; +import info.novatec.inspectit.rcp.view.tree.TreeViewerComparator; +import info.novatec.inspectit.storage.IStorageData; +import info.novatec.inspectit.storage.LocalStorageData; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.IJobChangeListener; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.window.ToolTip; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.ui.forms.widgets.Form; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.part.ViewPart; + +import com.google.common.base.Objects; + +/** + * Data explorer view show one Agent from a given {@link RepositoryDefinition}. Other agents can be + * selected via view menu. + * + * @author Ivan Senic + * + */ +public class DataExplorerView extends ViewPart implements CmrRepositoryChangeListener, StorageChangeListener, IRefreshableView { + + /** + * ID of the refresh contribution item needed for setting the visibility. + */ + private static final String REFRESH_CONTRIBUTION_ITEM = "info.novatec.inspectit.rcp.view.dataExplorer.refresh"; + + /** + * ID of the refresh contribution item needed for setting the visibility. + */ + private static final String CLEAR_BUFFER_CONTRIBUTION_ITEM = "info.novatec.inspectit.rcp.view.dataExplorer.clearBuffer"; + + /** + * ID of this view. + */ + public static final String VIEW_ID = "info.novatec.inspectit.rcp.view.dataExplorer"; + + /** + * Displayed repository. + */ + private RepositoryDefinition displayedRepositoryDefinition; + + /** + * Displayed agent. + */ + private PlatformIdent displayedAgent; + + /** + * Available agents for displaying. + */ + private List availableAgents; + + /** + * Cashed statuses of CMR repository definitions. + */ + private ConcurrentHashMap cachedOnlineStatus = new ConcurrentHashMap(); + + /** + * Listener for tree double clicks. + */ + private final TreeViewDoubleClickListener treeViewDoubleClickListener = new TreeViewDoubleClickListener(); + + /** + * Toolkit used for the view components. + */ + private FormToolkit toolkit; + + /** + * Main form for display of the repository. + */ + private Form mainForm; + + /** + * Tree in the form for the agents representation. + */ + private DeferredTreeViewer treeViewer; + + /** + * Composite used for message displaying. + */ + private Composite messageComposite; + + /** + * Collapse action. + */ + private CollapseAction collapseAction; + + /** + * Adapter to publish the selection to the Site. + */ + private SelectionProviderAdapter selectionProviderAdapter = new SelectionProviderAdapter(); + + /** + * Combo where agents are displayed. + */ + private Combo agentsCombo; + + /** + * Toolbar manager for the view. + */ + private IToolBarManager toolBarManager; + + /** + * Map of the cached expanded objects in the agent tree per agent/repository combination. Key + * for this map is the combined hash code that can be obtained by calling method + * {@link #getHashCodeForAgentRepository(PlatformIdent, RepositoryDefinition)}. + */ + private Map> expandedElementsPerAgent = new ConcurrentHashMap>(); + + /** + * If the inactive instrumentations should be hidden. + */ + private boolean hideInactiveInstrumentations = true; + + /** + * Default constructor. + */ + public DataExplorerView() { + InspectIT.getDefault().getCmrRepositoryManager().addCmrRepositoryChangeListener(this); + InspectIT.getDefault().getInspectITStorageManager().addStorageChangeListener(this); + } + + /** + * {@inheritDoc} + */ + @Override + public void createPartControl(Composite parent) { + createViewToolbar(); + + toolkit = new FormToolkit(parent.getDisplay()); + mainForm = toolkit.createForm(parent); + mainForm.getBody().setLayout(new GridLayout(1, true)); + createHeadClient(); + toolkit.decorateFormHeading(mainForm); + + Tree tree = toolkit.createTree(mainForm.getBody(), SWT.V_SCROLL | SWT.H_SCROLL); + treeViewer = new DeferredTreeViewer(tree); + treeViewer.setContentProvider(new TreeContentProvider()); + treeViewer.setLabelProvider(new TreeLabelProvider()); + treeViewer.setComparator(new TreeViewerComparator()); + treeViewer.addDoubleClickListener(treeViewDoubleClickListener); + ColumnViewerToolTipSupport.enableFor(treeViewer, ToolTip.NO_RECREATE); + + updateFormTitle(); + updateFormBody(); + updateAgentsCombo(); + + RepositoryDefinition lastSelectedRepositoryDefinition = PreferencesUtils.getObject(PreferencesConstants.LAST_SELECTED_REPOSITORY); + if (null != lastSelectedRepositoryDefinition) { + showRepository(lastSelectedRepositoryDefinition, null); + if (CollectionUtils.isNotEmpty(availableAgents)) { + long lastSelectedAgentId = PreferencesUtils.getLongValue(PreferencesConstants.LAST_SELECTED_AGENT); + for (PlatformIdent platformIdent : availableAgents) { + if (platformIdent.getId().longValue() == lastSelectedAgentId) { + selectAgentForDisplay(platformIdent); + performUpdate(); + break; + } + } + } + } + + getSite().setSelectionProvider(selectionProviderAdapter); + } + + /** + * Show the given repository on the view. If the selected agent is not provided, the arbitrary + * agent will be shown. + * + * @param repositoryDefinition + * Repository definition to display. + * @param agent + * Agent to select. Can be null. If the repository does not + */ + public void showRepository(final RepositoryDefinition repositoryDefinition, final PlatformIdent agent) { + Display.getDefault().syncExec(new Runnable() { + @Override + public void run() { + mainForm.setBusy(true); + if (null != displayedAgent && null != displayedRepositoryDefinition) { + cacheExpandedObjects(displayedAgent, displayedRepositoryDefinition); + } + updateFormTitle(); + agentsCombo.removeAll(); + displayMessage("Loading agents for repository " + repositoryDefinition.getName(), Display.getDefault().getSystemImage(SWT.ICON_WORKING)); + } + }); + displayedRepositoryDefinition = repositoryDefinition; + + PreferencesUtils.saveObject(PreferencesConstants.LAST_SELECTED_REPOSITORY, displayedRepositoryDefinition, false); + updateAvailableAgents(repositoryDefinition, new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + selectAgentForDisplay(agent); + + StructuredSelection ss = new StructuredSelection(repositoryDefinition); + selectionProviderAdapter.setSelection(ss); + + performUpdate(); + } + }); + + Display.getDefault().syncExec(new Runnable() { + @Override + public void run() { + mainForm.setBusy(false); + } + }); + } + + /** + * Selects the provided agent for display, if it is in the {@link #availableAgents} list. If + * not, a arbitrary agent will be selected if any is available. + * + * @param agent + * Hint for agent selection. + */ + private void selectAgentForDisplay(PlatformIdent agent) { + Display.getDefault().syncExec(new Runnable() { + @Override + public void run() { + mainForm.setBusy(true); + displayMessage("Loading agent tree..", Display.getDefault().getSystemImage(SWT.ICON_WORKING)); + } + }); + try { + if (null != agent && CollectionUtils.isNotEmpty(availableAgents) && availableAgents.contains(agent)) { + displayedAgent = displayedRepositoryDefinition.getGlobalDataAccessService().getCompleteAgent(agent.getId()); + PreferencesUtils.saveLongValue(PreferencesConstants.LAST_SELECTED_AGENT, agent.getId().longValue(), false); + } else if (CollectionUtils.isNotEmpty(availableAgents)) { + agent = availableAgents.iterator().next(); + displayedAgent = displayedRepositoryDefinition.getGlobalDataAccessService().getCompleteAgent(agent.getId()); + } else { + displayedAgent = null; // NOPMD + } + } catch (ServiceException e) { + InspectIT.getDefault().createErrorDialog("Exception occurred trying to load the agent tree for the agent " + agent.getAgentName() + ".", e, -1); + displayedAgent = null; // NOPMD + } + Display.getDefault().syncExec(new Runnable() { + @Override + public void run() { + mainForm.setBusy(false); + } + }); + } + + /** + * Caches the current expanded objects in the tree viewer with the given platform + * ident/repository combination. Note that this method will filter out the elements given by + * {@link org.eclipse.jface.viewers.TreeViewer#getExpandedElements()}, so that only the last + * expanded element in the tree is saved. + * + * @param platformIdent + * {@link PlatformIdent} to cache elements for. + * @param repositoryDefinition + * Repository that platform is belonging to. + */ + private void cacheExpandedObjects(PlatformIdent platformIdent, RepositoryDefinition repositoryDefinition) { + Object[] allExpanded = treeViewer.getExpandedElements(); + if (allExpanded.length > 0) { + Set parents = new HashSet(); + for (Object expanded : allExpanded) { + Object parent = ((ITreeContentProvider) treeViewer.getContentProvider()).getParent(expanded); + while (parent != null) { + parents.add(parent); + parent = ((ITreeContentProvider) treeViewer.getContentProvider()).getParent(parent); + } + } + List expandedList = new ArrayList(Arrays.asList(allExpanded)); + expandedList.removeAll(parents); + expandedElementsPerAgent.put(getHashCodeForAgentRepository(platformIdent, repositoryDefinition), expandedList); + } else { + expandedElementsPerAgent.put(getHashCodeForAgentRepository(platformIdent, repositoryDefinition), Collections.emptyList()); + } + } + + /** + * Returns the hash code combination for {@link PlatformIdent} and {@link RepositoryDefinition}. + * + * @param platformIdent + * {@link PlatformIdent} + * @param repositoryDefinition + * {@link RepositoryDefinition} + * @return The hash code as int. + */ + private int getHashCodeForAgentRepository(PlatformIdent platformIdent, RepositoryDefinition repositoryDefinition) { + return Objects.hashCode(platformIdent, repositoryDefinition); + } + + /** + * Updates the list of available agents. + * + * @param repositoryDefinition + * {@link RepositoryDefinition}. + * @param jobListener + * the listener. + */ + private void updateAvailableAgents(final RepositoryDefinition repositoryDefinition, IJobChangeListener jobListener) { + Job updateAvailableAgentsJob = new Job("Updating Available Agents") { + @Override + protected IStatus run(IProgressMonitor monitor) { + if (repositoryDefinition instanceof CmrRepositoryDefinition) { + CmrRepositoryDefinition cmrRepositoryDefinition = (CmrRepositoryDefinition) repositoryDefinition; + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + availableAgents = new ArrayList(cmrRepositoryDefinition.getGlobalDataAccessService().getAgentsOverview().keySet()); + } else { + availableAgents = null; // NOPMD + } + } else if (repositoryDefinition instanceof StorageRepositoryDefinition) { + StorageRepositoryDefinition storageRepositoryDefinition = (StorageRepositoryDefinition) repositoryDefinition; + if (storageRepositoryDefinition.getLocalStorageData().isFullyDownloaded() || storageRepositoryDefinition.getCmrRepositoryDefinition().getOnlineStatus() != OnlineStatus.OFFLINE) { + availableAgents = new ArrayList(storageRepositoryDefinition.getGlobalDataAccessService().getAgentsOverview().keySet()); + } else { + availableAgents = null; // NOPMD + } + } else { + availableAgents = null; // NOPMD + } + if (CollectionUtils.isNotEmpty(availableAgents)) { + Collections.sort(availableAgents, new Comparator() { + @Override + public int compare(PlatformIdent o1, PlatformIdent o2) { + return ObjectUtils.compare(o1.getAgentName(), o2.getAgentName()); + } + }); + } + return Status.OK_STATUS; + } + }; + if (null != jobListener) { + updateAvailableAgentsJob.addJobChangeListener(jobListener); + } + updateAvailableAgentsJob.schedule(); + } + + /** + * Creates view toolbar. + */ + private void createViewToolbar() { + toolBarManager = getViewSite().getActionBars().getToolBarManager(); + + ShowHideInactiveInstrumentationsAction showHideInactiveInstrumentationsAction = new ShowHideInactiveInstrumentationsAction(); + toolBarManager.add(showHideInactiveInstrumentationsAction); + + collapseAction = new CollapseAction(); + toolBarManager.add(collapseAction); + } + + /** + * Creates the head client that holds the agents in combo box. + */ + private void createHeadClient() { + Composite headClient = new Composite(mainForm.getHead(), SWT.NONE); + GridLayout gl = new GridLayout(2, false); + gl.marginHeight = 0; + gl.marginWidth = 0; + headClient.setLayout(gl); + + Label agentImg = new Label(headClient, SWT.NONE); + agentImg.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_AGENT)); + + agentsCombo = new Combo(headClient, SWT.READ_ONLY | SWT.BORDER | SWT.DROP_DOWN); + agentsCombo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + agentsCombo.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + int selected = agentsCombo.getSelectionIndex(); + if (selected < availableAgents.size()) { + PlatformIdent platformIdent = availableAgents.get(selected); + if (!ObjectUtils.equals(displayedAgent, platformIdent)) { + if (null != displayedAgent && null != displayedRepositoryDefinition) { + cacheExpandedObjects(displayedAgent, displayedRepositoryDefinition); + } + selectAgentForDisplay(platformIdent); + performUpdate(); + } + } + } + }); + + mainForm.setHeadClient(headClient); + } + + /** + * Updates the combo menu with agents. + */ + private void updateAgentsCombo() { + agentsCombo.removeAll(); + if (null != availableAgents && !availableAgents.isEmpty()) { + agentsCombo.setEnabled(true); + int i = 0; + int selectedIndex = -1; + for (PlatformIdent platformIdent : availableAgents) { + agentsCombo.add(TextFormatter.getAgentDescription(platformIdent)); + if (ObjectUtils.equals(platformIdent, displayedAgent)) { + selectedIndex = i; + } + i++; + } + if (-1 != selectedIndex) { + agentsCombo.select(selectedIndex); + } + } else { + agentsCombo.setEnabled(false); + } + mainForm.getHead().layout(); + } + + /** + * Updates the form title. + */ + private void updateFormTitle() { + if (null != displayedRepositoryDefinition) { + if (displayedRepositoryDefinition instanceof CmrRepositoryDefinition) { + CmrRepositoryDefinition cmrRepositoryDefinition = (CmrRepositoryDefinition) displayedRepositoryDefinition; + mainForm.setImage(ImageFormatter.getCmrRepositoryImage(cmrRepositoryDefinition, true)); + mainForm.setText(cmrRepositoryDefinition.getName()); + mainForm.setToolTipText(getCmrRepositoryDescription(cmrRepositoryDefinition)); + } else if (displayedRepositoryDefinition instanceof StorageRepositoryDefinition) { + StorageRepositoryDefinition storageRepositoryDefinition = (StorageRepositoryDefinition) displayedRepositoryDefinition; + mainForm.setImage(ImageFormatter.getStorageRepositoryImage(storageRepositoryDefinition)); + mainForm.setText(storageRepositoryDefinition.getName()); + mainForm.setToolTipText(getStorageDescirption(storageRepositoryDefinition)); + } + mainForm.setMessage(null); + } else { + mainForm.setImage(null); + mainForm.setText("No repository loaded"); + mainForm.setMessage("Repositories can be loaded from Repository or Storage Manager", IMessageProvider.WARNING); + mainForm.setToolTipText(null); + } + } + + /** + * Updates the tree input and refreshes the tree. + */ + + private void updateFormBody() { + clearFormBody(); + if (null != displayedRepositoryDefinition && null != displayedAgent) { + TreeModelManager treeModelManager = null; + treeModelManager = new TreeModelManager(displayedRepositoryDefinition, displayedAgent, hideInactiveInstrumentations); + if (null != treeModelManager && null != displayedAgent) { + treeViewer.setInput(treeModelManager); + treeViewer.getTree().setVisible(true); + treeViewer.getTree().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + } else { + displayMessage("Repository is currently unavailable.", Display.getDefault().getSystemImage(SWT.ICON_ERROR)); + } + } else if (null != displayedRepositoryDefinition && null == displayedAgent) { + if (null == availableAgents) { + displayMessage("No agent could be loaded on selected repository.", Display.getDefault().getSystemImage(SWT.ICON_WARNING)); + } else { + displayMessage("This repository is empty.", Display.getDefault().getSystemImage(SWT.ICON_INFORMATION)); + } + } + + mainForm.getBody().layout(); + } + + /** + * Updates view tool-bar. + */ + private void updateViewToolbar() { + collapseAction.updateEnabledState(); + toolBarManager.find(REFRESH_CONTRIBUTION_ITEM).setVisible(displayedRepositoryDefinition instanceof CmrRepositoryDefinition); + toolBarManager.find(CLEAR_BUFFER_CONTRIBUTION_ITEM).setVisible( + displayedRepositoryDefinition instanceof CmrRepositoryDefinition && !OnlineStatus.OFFLINE.equals(((CmrRepositoryDefinition) displayedRepositoryDefinition).getOnlineStatus())); + toolBarManager.update(true); + } + + /** + * Clears the look of the form. + */ + private void clearFormBody() { + if (messageComposite != null && !messageComposite.isDisposed()) { + messageComposite.dispose(); + } + treeViewer.setInput(null); + treeViewer.getTree().setVisible(false); + treeViewer.getTree().setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + } + + /** + * Updates the form. + */ + public void performUpdate() { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + mainForm.setBusy(true); + updateFormTitle(); + updateFormBody(); + updateAgentsCombo(); + updateViewToolbar(); + if (null != displayedAgent) { + List expandedObjects = expandedElementsPerAgent.get(getHashCodeForAgentRepository(displayedAgent, displayedRepositoryDefinition)); + if (null != expandedObjects) { + for (Object object : expandedObjects) { + treeViewer.expandObject(object, 1); + } + } + } + mainForm.setBusy(false); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void setFocus() { + if (treeViewer.getTree().isVisible()) { + treeViewer.getTree().setFocus(); + } else { + mainForm.setFocus(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void refresh() { + if (displayedRepositoryDefinition instanceof CmrRepositoryDefinition) { + if (null != displayedAgent) { + cacheExpandedObjects(displayedAgent, displayedRepositoryDefinition); + } + final UpdateRepositoryJob job = InspectIT.getDefault().getCmrRepositoryManager().forceCmrRepositoryOnlineStatusUpdate((CmrRepositoryDefinition) displayedRepositoryDefinition); + job.addJobChangeListener(new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + updateAvailableAgents(displayedRepositoryDefinition, new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + if (null != availableAgents && !availableAgents.isEmpty() && null != displayedAgent) { + boolean found = false; + for (PlatformIdent platformIdent : availableAgents) { + if (platformIdent.getId().longValue() == displayedAgent.getId()) { + selectAgentForDisplay(platformIdent); + found = true; + break; + } + } + if (!found) { + selectAgentForDisplay(availableAgents.get(0)); + } + } else if (null != availableAgents && !availableAgents.isEmpty() && null == displayedAgent) { + selectAgentForDisplay(availableAgents.get(0)); + } else { + selectAgentForDisplay(null); + } + performUpdate(); + } + }); + job.removeJobChangeListener(this); + } + }); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canRefresh() { + return true; + } + + /** + * {@inheritDoc} + */ + public void repositoryOnlineStatusUpdated(CmrRepositoryDefinition repositoryDefinition, OnlineStatus oldStatus, OnlineStatus newStatus) { + if (newStatus != OnlineStatus.CHECKING) { + boolean shouldUpdate = ObjectUtils.equals(displayedRepositoryDefinition, repositoryDefinition); + if (displayedRepositoryDefinition instanceof StorageRepositoryDefinition) { + shouldUpdate |= ObjectUtils.equals(((StorageRepositoryDefinition) displayedRepositoryDefinition).getCmrRepositoryDefinition(), repositoryDefinition); + } + if (shouldUpdate) { + OnlineStatus cachedStatus = cachedOnlineStatus.get(repositoryDefinition); + if (cachedStatus == OnlineStatus.OFFLINE && newStatus == OnlineStatus.ONLINE) { + updateAvailableAgents(displayedRepositoryDefinition, new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + mainForm.setBusy(true); + updateFormTitle(); + updateFormBody(); + updateAgentsCombo(); + updateViewToolbar(); + mainForm.setBusy(false); + } + }); + } + }); + } else if (cachedStatus == OnlineStatus.ONLINE && newStatus == OnlineStatus.OFFLINE) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + mainForm.setBusy(true); + updateFormTitle(); + updateAgentsCombo(); + updateViewToolbar(); + mainForm.setBusy(false); + } + }); + } + } + cachedOnlineStatus.put(repositoryDefinition, newStatus); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryDataUpdated(CmrRepositoryDefinition cmrRepositoryDefinition) { + if (ObjectUtils.equals(cmrRepositoryDefinition, displayedRepositoryDefinition)) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + mainForm.setBusy(true); + updateFormTitle(); + mainForm.setBusy(false); + } + }); + } + } + + /** + * {@inheritDoc} + */ + public void repositoryAdded(CmrRepositoryDefinition cmrRepositoryDefinition) { + } + + /** + * {@inheritDoc} + */ + public void repositoryRemoved(CmrRepositoryDefinition cmrRepositoryDefinition) { + if (ObjectUtils.equals(cmrRepositoryDefinition, displayedRepositoryDefinition)) { + displayedRepositoryDefinition = null; // NOPMD + displayedAgent = null; // NOPMD + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + performUpdate(); + } + }); + } else if (displayedRepositoryDefinition instanceof StorageRepositoryDefinition) { + StorageRepositoryDefinition storageRepositoryDefinition = (StorageRepositoryDefinition) displayedRepositoryDefinition; + if (ObjectUtils.equals(cmrRepositoryDefinition, storageRepositoryDefinition.getCmrRepositoryDefinition()) && !storageRepositoryDefinition.getLocalStorageData().isFullyDownloaded()) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + agentsCombo.removeAll(); + agentsCombo.setEnabled(false); + displayMessage("CMR Repository for selected storage was removed.", Display.getDefault().getSystemImage(SWT.ICON_WARNING)); + } + }); + } + } + } + + /** + * {@inheritDoc} + */ + public void repositoryAgentDeleted(CmrRepositoryDefinition cmrRepositoryDefinition, PlatformIdent agent) { + if (ObjectUtils.equals(cmrRepositoryDefinition, displayedRepositoryDefinition)) { + if (ObjectUtils.equals(agent, displayedAgent)) { + displayedAgent = null; // NOPMD + } + availableAgents.remove(agent); + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + performUpdate(); + } + }); + } + } + + /** + * {@inheritDoc} + */ + public void storageDataUpdated(IStorageData storageData) { + if (displayedRepositoryDefinition instanceof StorageRepositoryDefinition) { + final StorageRepositoryDefinition storageRepositoryDefinition = (StorageRepositoryDefinition) displayedRepositoryDefinition; + if (ObjectUtils.equals(storageData.getId(), storageRepositoryDefinition.getLocalStorageData().getId())) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + updateFormTitle(); + } + }); + } + } + } + + /** + * {@inheritDoc} + */ + public void storageRemotelyDeleted(IStorageData storageData) { + if (displayedRepositoryDefinition instanceof StorageRepositoryDefinition) { + final StorageRepositoryDefinition storageRepositoryDefinition = (StorageRepositoryDefinition) displayedRepositoryDefinition; + if (!storageRepositoryDefinition.getLocalStorageData().isFullyDownloaded() && ObjectUtils.equals(storageData.getId(), storageRepositoryDefinition.getLocalStorageData().getId())) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + agentsCombo.removeAll(); + agentsCombo.setEnabled(false); + displayMessage("Selected storage was remotely deleted and is not available anymore.", Display.getDefault().getSystemImage(SWT.ICON_WARNING)); + } + }); + } + } + } + + /** + * {@inheritDoc} + */ + public void storageLocallyDeleted(IStorageData storageData) { + if (displayedRepositoryDefinition instanceof StorageRepositoryDefinition) { + final StorageRepositoryDefinition storageRepositoryDefinition = (StorageRepositoryDefinition) displayedRepositoryDefinition; + if (ObjectUtils.equals(storageData.getId(), storageRepositoryDefinition.getLocalStorageData().getId())) { + if (InspectIT.getDefault().getInspectITStorageManager().getMountedAvailableStorages().contains(storageData)) { + // if remote one is available, just update + performUpdate(); + } else { + // if it is not available on the CMR, remove everything + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + agentsCombo.removeAll(); + agentsCombo.setEnabled(false); + displayMessage("Selected storage was locally deleted and is not available anymore.", Display.getDefault().getSystemImage(SWT.ICON_WARNING)); + } + }); + } + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + InspectIT.getDefault().getCmrRepositoryManager().removeCmrRepositoryChangeListener(this); + InspectIT.getDefault().getInspectITStorageManager().removeStorageChangeListener(this); + super.dispose(); + } + + /** + * Displays the message on the provided composite. + * + * @param text + * Text of message. + * @param image + * Image to show. + */ + private void displayMessage(String text, Image image) { + clearFormBody(); + if (null == messageComposite || messageComposite.isDisposed()) { + messageComposite = toolkit.createComposite(mainForm.getBody()); + } else { + for (Control c : messageComposite.getChildren()) { + if (!c.isDisposed()) { + c.dispose(); + } + } + } + messageComposite.setLayout(new GridLayout(2, false)); + messageComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + toolkit.createLabel(messageComposite, null).setImage(image); + toolkit.createLabel(messageComposite, text, SWT.WRAP).setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); + mainForm.getBody().layout(); + } + + /** + * Returns storage description for title box. + * + * @param storageRepositoryDefinition + * {@link StorageRepositoryDefinition} + * @return Description for title box. + */ + private String getStorageDescirption(StorageRepositoryDefinition storageRepositoryDefinition) { + LocalStorageData localStorageData = storageRepositoryDefinition.getLocalStorageData(); + if (localStorageData.isFullyDownloaded()) { + return "Storage Repository - Accessible offline"; + } else { + return "Storage Repository - Accessible via CMR repository"; + } + } + + /** + * Description of the {@link CmrRepositoryDefinition}. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + * @return Description in form http://ip:port + */ + private String getCmrRepositoryDescription(CmrRepositoryDefinition cmrRepositoryDefinition) { + return "Central Management Repository @ http://" + cmrRepositoryDefinition.getIp() + ":" + cmrRepositoryDefinition.getPort(); + } + + /** + * Action that collapses all agents. + * + * @author Ivan Senic + * + */ + private class CollapseAction extends Action { + + /** + * Default constructor. + */ + public CollapseAction() { + setImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_COLLAPSE)); + setToolTipText("Collapse All"); + updateEnabledState(); + } + + /** + * Updates the enabled state of action based on the currently selected + * {@link CmrRepositoryDefinition}. + */ + public final void updateEnabledState() { + if (null != treeViewer && treeViewer.getInput() != null) { + setEnabled(true); + } else { + setEnabled(false); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + treeViewer.setExpandedElements(new Object[0]); + treeViewer.refresh(); + } + + } + + /** + * Class for handling the showing / hiding of the inactive instrumentations. + * + * @author Ivan Senic + * + */ + private class ShowHideInactiveInstrumentationsAction extends Action { + + /** + * Default constructor. + */ + public ShowHideInactiveInstrumentationsAction() { + super(null, AS_CHECK_BOX); + setImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_INSTRUMENTATION_BROWSER_INACTIVE)); + setChecked(!hideInactiveInstrumentations); + updateToolTipText(); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + hideInactiveInstrumentations = !isChecked(); // NOPMD + // Bug in PMD reporting inverting of boolean + updateToolTipText(); + if (null != displayedAgent && null != displayedRepositoryDefinition) { + cacheExpandedObjects(displayedAgent, displayedRepositoryDefinition); + } + performUpdate(); + } + + /** + * Updates tool-tip text based on the current state. + */ + private void updateToolTipText() { + if (!isChecked()) { + setToolTipText("Show inactive instrumentations"); + } else { + setToolTipText("Hide inactive instrumentations"); + } + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/view/impl/RepositoryManagerView.java b/inspectIT/src/info/novatec/inspectit/rcp/view/impl/RepositoryManagerView.java new file mode 100644 index 000000000..9971ec75a --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/view/impl/RepositoryManagerView.java @@ -0,0 +1,961 @@ +package info.novatec.inspectit.rcp.view.impl; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.communication.data.cmr.AgentStatusData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.tree.DeferredTreeViewer; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.form.CmrRepositoryPropertyForm; +import info.novatec.inspectit.rcp.formatter.ImageFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.handlers.ShowRepositoryHandler; +import info.novatec.inspectit.rcp.model.AgentLeaf; +import info.novatec.inspectit.rcp.model.Component; +import info.novatec.inspectit.rcp.model.DeferredAgentsComposite; +import info.novatec.inspectit.rcp.provider.ICmrRepositoryProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryChangeListener; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.repository.CmrRepositoryManager; +import info.novatec.inspectit.rcp.repository.CmrRepositoryManager.UpdateRepositoryJob; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; +import info.novatec.inspectit.rcp.view.IRefreshableView; +import info.novatec.inspectit.rcp.view.tree.TreeContentProvider; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.jface.window.ToolTip; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.forms.widgets.Form; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.handlers.IHandlerService; +import org.eclipse.ui.part.ViewPart; +import org.eclipse.ui.progress.IProgressConstants; +import org.eclipse.ui.progress.UIJob; + +/** + * Repository manager view where user can work with repositories, check agents, and give input for + * the data explorer view. + * + * @author Ivan Senic + * + */ +public class RepositoryManagerView extends ViewPart implements IRefreshableView, CmrRepositoryChangeListener { + + /** + * ID of this view. + */ + public static final String VIEW_ID = "info.novatec.inspectit.rcp.view.repositoryManager"; + + /** + * ID for tree menu. + */ + private static final String MENU_ID = "info.novatec.inspectit.rcp.view.repositoryManager.repositoryTree"; + + /** + * {@link CmrRepositoryManager}. + */ + private CmrRepositoryManager cmrRepositoryManager; + + /** + * Input list. + */ + private List inputList = new ArrayList(); + + /** + * Online statuses map. + */ + private Map cachedStatusMap = new ConcurrentHashMap(); + + /** + * Toolkit. + */ + private FormToolkit toolkit; + + /** + * Form for the view. + */ + private Form mainForm; + + /** + * Tree Viewer. + */ + private DeferredTreeViewer treeViewer; + + /** + * Composite for displaying the messages. + */ + private Composite messageComposite; + + /** + * CMR property form. + */ + private CmrRepositoryPropertyForm cmrPropertyForm; + + /** + * Views main composite. + */ + private SashForm mainComposite; + + /** + * Boolean for layout of view. + */ + private boolean verticaLayout = true; + + /** + * Last selected repository, so that the selection can be maintained after the view is + * refreshed. + */ + private DeferredAgentsComposite lastSelectedRepository = null; + + /** + * Defines if agents are shown in the tree which have not sent any data since the CMR was + * started. + */ + private boolean showOldAgents = false; + + /** + * {@link AgentStatusUpdateJob}. + */ + private AgentStatusUpdateJob agentStatusUpdateJob; + + /** + * List of the objects that is expanded in the tree. + */ + private List expandedList; + + /** + * Default constructor. + */ + public RepositoryManagerView() { + cmrRepositoryManager = InspectIT.getDefault().getCmrRepositoryManager(); + cmrRepositoryManager.addCmrRepositoryChangeListener(this); + createInputList(); + } + + /** + * {@inheritDoc} + */ + @Override + public void createPartControl(Composite parent) { + createViewToolbar(); + + toolkit = new FormToolkit(parent.getDisplay()); + + mainComposite = new SashForm(parent, SWT.VERTICAL); + GridLayout mainLayout = new GridLayout(1, true); + mainLayout.marginWidth = 0; + mainLayout.marginHeight = 0; + mainComposite.setLayout(mainLayout); + + mainForm = toolkit.createForm(mainComposite); + mainForm.getBody().setLayout(new GridLayout(1, true)); + mainForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + toolkit.decorateFormHeading(mainForm); + + Tree tree = toolkit.createTree(mainForm.getBody(), SWT.V_SCROLL | SWT.H_SCROLL); + treeViewer = new DeferredTreeViewer(tree); + + // create tree content provider + TreeContentProvider treeContentProvider = new TreeContentProvider() { + @SuppressWarnings("unchecked") + @Override + public Object[] getElements(Object inputElement) { + if (inputElement instanceof Object[]) { + return (Object[]) inputElement; + } + if (inputElement instanceof Collection) { + return ((Collection) inputElement).toArray(); + } + return new Object[0]; + } + }; + // add the listener that will expand all levels of the agent tree that were expanded before + // the update + treeContentProvider.addUpdateCompleteListener(new ExpandFoldersUpdateCompleteListener()); + treeViewer.setContentProvider(treeContentProvider); + + treeViewer.setLabelProvider(new RepositoryTreeLabelProvider()); + treeViewer.setComparator(new ViewerComparator() { + @Override + public int compare(Viewer viewer, Object e1, Object e2) { + if (e1 instanceof info.novatec.inspectit.rcp.model.Composite && !(e2 instanceof info.novatec.inspectit.rcp.model.Composite)) { + return -1; + } else if (!(e1 instanceof info.novatec.inspectit.rcp.model.Composite) && e2 instanceof info.novatec.inspectit.rcp.model.Composite) { + return 1; + } else if (e1 instanceof Component && e2 instanceof Component) { + return ((Component) e1).getName().compareToIgnoreCase(((Component) e2).getName()); + } else if (e1 instanceof Component) { + return 1; + } else if (e2 instanceof Component) { + return -1; + } else { + return 0; + } + + } + }); + treeViewer.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + StructuredSelection structuredSelection = (StructuredSelection) event.getSelection(); + if (structuredSelection.getFirstElement() instanceof DeferredAgentsComposite) { + lastSelectedRepository = (DeferredAgentsComposite) structuredSelection.getFirstElement(); + } + } + }); + treeViewer.addDoubleClickListener(new RepositoryManagerDoubleClickListener()); + ColumnViewerToolTipSupport.enableFor(treeViewer, ToolTip.NO_RECREATE); + treeViewer.setInput(inputList); + + cmrPropertyForm = new CmrRepositoryPropertyForm(mainComposite); + cmrPropertyForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + treeViewer.addSelectionChangedListener(cmrPropertyForm); + + MenuManager menuManager = new MenuManager(); + menuManager.setRemoveAllWhenShown(true); + getSite().registerContextMenu(MENU_ID, menuManager, treeViewer); + Control control = treeViewer.getControl(); + Menu menu = menuManager.createContextMenu(control); + control.setMenu(menu); + + mainComposite.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + int width = mainComposite.getBounds().width; + int height = mainComposite.getBounds().height; + + if (width > height && verticaLayout) { + verticaLayout = false; + mainComposite.setOrientation(SWT.HORIZONTAL); + } else if (width < height && !verticaLayout) { + verticaLayout = true; + mainComposite.setOrientation(SWT.VERTICAL); + } + + mainComposite.layout(); + } + }); + + updateFormBody(); + mainComposite.setWeights(new int[] { 2, 3 }); + + agentStatusUpdateJob = new AgentStatusUpdateJob(); + + getSite().setSelectionProvider(treeViewer); + } + + /** + * Creates the view tool-bar. + */ + private void createViewToolbar() { + IToolBarManager toolBarManager = getViewSite().getActionBars().getToolBarManager(); + toolBarManager.add(new ShowAgentsAction()); + toolBarManager.add(new ShowPropertiesAction()); + toolBarManager.add(new Separator()); + } + + /** + * Updates the repository map. + */ + private void createInputList() { + inputList.clear(); + List repositories = cmrRepositoryManager.getCmrRepositoryDefinitions(); + for (CmrRepositoryDefinition cmrRepositoryDefinition : repositories) { + inputList.add(new DeferredAgentsComposite(cmrRepositoryDefinition, showOldAgents)); + OnlineStatus onlineStatus = cmrRepositoryDefinition.getOnlineStatus(); + if (onlineStatus == OnlineStatus.ONLINE || onlineStatus == OnlineStatus.OFFLINE) { + cachedStatusMap.put(cmrRepositoryDefinition, onlineStatus); + } + } + } + + /** + * Updates body. + */ + private void updateFormBody() { + clearFormBody(); + if (!inputList.isEmpty()) { + treeViewer.getTree().setVisible(true); + treeViewer.getTree().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + treeViewer.setInput(inputList); + treeViewer.expandAll(); + if (null != lastSelectedRepository && inputList.contains(lastSelectedRepository)) { + StructuredSelection ss = new StructuredSelection(lastSelectedRepository); + treeViewer.setSelection(ss, true); + } + } else { + displayMessage("No CMR repository present. Please add the CMR repository via 'Add CMR repository' action.", Display.getDefault().getSystemImage(SWT.ICON_INFORMATION)); + } + mainForm.getBody().layout(); + } + + /** + * Clears the look of the forms body. + */ + private void clearFormBody() { + if (messageComposite != null && !messageComposite.isDisposed()) { + messageComposite.dispose(); + } + treeViewer.setInput(Collections.emptyList()); + treeViewer.getTree().setVisible(false); + treeViewer.getTree().setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + } + + /** + * Displays the message on the provided composite. + * + * @param text + * Text of message. + * @param image + * Image to show. + */ + private void displayMessage(String text, Image image) { + if (null == messageComposite || messageComposite.isDisposed()) { + messageComposite = toolkit.createComposite(mainForm.getBody()); + } else { + for (Control c : messageComposite.getChildren()) { + if (!c.isDisposed()) { + c.dispose(); + } + } + } + messageComposite.setLayout(new GridLayout(2, false)); + messageComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + toolkit.createLabel(messageComposite, null).setImage(image); + toolkit.createLabel(messageComposite, text, SWT.WRAP).setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); + } + + /** + * {@inheritDoc} + */ + @Override + public void setFocus() { + if (treeViewer.getTree().isVisible()) { + treeViewer.getTree().setFocus(); + } else { + mainForm.setFocus(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryOnlineStatusUpdated(final CmrRepositoryDefinition repositoryDefinition, OnlineStatus oldStatus, OnlineStatus newStatus) { + if (newStatus != OnlineStatus.CHECKING) { + OnlineStatus cachedStatus = cachedStatusMap.get(repositoryDefinition); + if (null != cachedStatus) { + if (cachedStatus != newStatus) { // NOPMD + Display.getDefault().asyncExec(new Runnable() { + + @Override + public void run() { + mainForm.setBusy(true); + for (DeferredAgentsComposite composite : inputList) { + if (ObjectUtils.equals(composite.getRepositoryDefinition(), repositoryDefinition)) { + treeViewer.refresh(composite, true); + treeViewer.expandAll(); + if (ObjectUtils.equals(composite, lastSelectedRepository)) { + if (null != lastSelectedRepository && inputList.contains(lastSelectedRepository)) { + treeViewer.setSelection(StructuredSelection.EMPTY); + StructuredSelection ss = new StructuredSelection(lastSelectedRepository); + treeViewer.setSelection(ss, true); + } + } + } + } + mainForm.setBusy(false); + } + }); + } + } + cachedStatusMap.put(repositoryDefinition, newStatus); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryDataUpdated(final CmrRepositoryDefinition cmrRepositoryDefinition) { + Display.getDefault().asyncExec(new Runnable() { + + @Override + public void run() { + mainForm.setBusy(true); + for (DeferredAgentsComposite composite : inputList) { + if (ObjectUtils.equals(composite.getRepositoryDefinition(), cmrRepositoryDefinition)) { + treeViewer.refresh(composite); + break; + } + } + mainForm.setBusy(false); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryAdded(CmrRepositoryDefinition cmrRepositoryDefinition) { + final DeferredAgentsComposite newComposite = new DeferredAgentsComposite(cmrRepositoryDefinition, showOldAgents); + inputList.add(newComposite); + OnlineStatus onlineStatus = cmrRepositoryDefinition.getOnlineStatus(); + cachedStatusMap.put(cmrRepositoryDefinition, onlineStatus); + + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + mainForm.setBusy(true); + if (inputList.size() > 1) { + treeViewer.refresh(); + if (null != lastSelectedRepository && inputList.contains(lastSelectedRepository)) { + StructuredSelection ss = new StructuredSelection(lastSelectedRepository); + treeViewer.setSelection(ss, true); + } + } else { + updateFormBody(); + } + mainForm.setBusy(false); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryRemoved(final CmrRepositoryDefinition cmrRepositoryDefinition) { + DeferredAgentsComposite toRemove = null; + for (DeferredAgentsComposite composite : inputList) { + if (ObjectUtils.equals(composite.getRepositoryDefinition(), cmrRepositoryDefinition)) { + toRemove = composite; + break; + } + } + if (null != toRemove) { + inputList.remove(toRemove); + } + cachedStatusMap.remove(cmrRepositoryDefinition); + + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + if (ObjectUtils.equals(cmrRepositoryDefinition, lastSelectedRepository.getRepositoryDefinition())) { + // reset selection if removed repository is selected + treeViewer.setSelection(StructuredSelection.EMPTY); + } + if (inputList.isEmpty()) { + updateFormBody(); + } else { + treeViewer.refresh(); + } + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryAgentDeleted(CmrRepositoryDefinition cmrRepositoryDefinition, PlatformIdent agent) { + DeferredAgentsComposite toUpdate = null; + for (DeferredAgentsComposite composite : inputList) { + if (ObjectUtils.equals(composite.getRepositoryDefinition(), cmrRepositoryDefinition)) { + toUpdate = composite; + break; + } + } + + if (null != toUpdate) { + final DeferredAgentsComposite finalToUpdate = toUpdate; + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + treeViewer.refresh(finalToUpdate, true); + if (ObjectUtils.equals(finalToUpdate, lastSelectedRepository)) { + treeViewer.setSelection(StructuredSelection.EMPTY); + StructuredSelection ss = new StructuredSelection(finalToUpdate); + treeViewer.setSelection(ss, true); + if (null != cmrPropertyForm && !cmrPropertyForm.isDisposed()) { + cmrPropertyForm.refresh(); + } + } + } + }); + } + } + + /** + * {@inheritDoc} + *

+ * Refreshes all repositories. + */ + public synchronized void refresh() { + // preserve what elements need to be expanded after refresh + Object[] expandedElements = treeViewer.getExpandedElements(); + Set parents = new HashSet(); + for (Object expanded : expandedElements) { + Object parent = ((ITreeContentProvider) treeViewer.getContentProvider()).getParent(expanded); + while (parent != null) { + parents.add(parent); + parent = ((ITreeContentProvider) treeViewer.getContentProvider()).getParent(parent); + } + } + expandedList = new ArrayList(Arrays.asList(expandedElements)); + expandedList.removeAll(parents); + + // execute refresh + Collection jobs = cmrRepositoryManager.forceAllCmrRepositoriesOnlineStatusUpdate(); + for (final UpdateRepositoryJob updateRepositoryJob : jobs) { + updateRepositoryJob.addJobChangeListener(new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + CmrRepositoryDefinition cmrRepositoryDefinition = updateRepositoryJob.getCmrRepositoryDefinition(); + DeferredAgentsComposite toUpdate = null; + for (DeferredAgentsComposite composite : inputList) { + if (ObjectUtils.equals(composite.getRepositoryDefinition(), cmrRepositoryDefinition)) { + toUpdate = composite; + break; + } + } + if (null != toUpdate) { + final DeferredAgentsComposite finalToUpdate = toUpdate; + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + treeViewer.refresh(finalToUpdate, true); + if (ObjectUtils.equals(finalToUpdate, lastSelectedRepository)) { + if (null != cmrPropertyForm && !cmrPropertyForm.isDisposed()) { + cmrPropertyForm.refresh(); + } + } + } + }); + } + updateRepositoryJob.removeJobChangeListener(this); + } + }); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canRefresh() { + return !inputList.isEmpty() || !cmrRepositoryManager.getCmrRepositoryDefinitions().isEmpty(); + } + + /** + * Show or hides properties. + * + * @param show + * Should properties be shown. + */ + public void setShowProperties(boolean show) { + if (show) { + CmrRepositoryDefinition cmrRepositoryDefinition = null; + StructuredSelection selection = (StructuredSelection) treeViewer.getSelection(); + if (!selection.isEmpty()) { + if (selection.getFirstElement() instanceof ICmrRepositoryProvider) { + cmrRepositoryDefinition = ((ICmrRepositoryProvider) selection.getFirstElement()).getCmrRepositoryDefinition(); + } + } + + cmrPropertyForm = new CmrRepositoryPropertyForm(mainComposite, cmrRepositoryDefinition); + cmrPropertyForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + treeViewer.addSelectionChangedListener(cmrPropertyForm); + mainComposite.setWeights(new int[] { 2, 3 }); + mainComposite.layout(); + } else { + if (null != cmrPropertyForm && !cmrPropertyForm.isDisposed()) { + treeViewer.removeSelectionChangedListener(cmrPropertyForm); + cmrPropertyForm.dispose(); + cmrPropertyForm = null; // NOPMD + } + mainComposite.setWeights(new int[] { 1 }); + mainComposite.layout(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + cmrRepositoryManager.removeCmrRepositoryChangeListener(this); + agentStatusUpdateJob.cancel(); + super.dispose(); + } + + /** + * Action for show hide properties. + * + * @author Ivan Senic + * + */ + private class ShowPropertiesAction extends Action { + + /** + * Default constructor. + */ + public ShowPropertiesAction() { + super(null, AS_CHECK_BOX); + setImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_PROPERTIES)); + setChecked(true); + setToolTipText("Hide Properties"); + } + + /** + * {@inheritDoc} + */ + public void run() { + if (isChecked()) { + setShowProperties(true); + setToolTipText("Hide Properties"); + } else { + setShowProperties(false); + setToolTipText("Show Properties"); + } + }; + } + + /** + * Action for show hide agents which have not sent any data. + * + * @author Patrice Bouillet + * + */ + private class ShowAgentsAction extends Action { + + /** + * Default constructor. + */ + public ShowAgentsAction() { + super(null, AS_CHECK_BOX); + setImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_AGENT)); + setChecked(showOldAgents); + updateToolTipText(); + } + + /** + * {@inheritDoc} + */ + public void run() { + showOldAgents = isChecked(); + updateToolTipText(); + createInputList(); + updateFormBody(); + }; + + /** + * Updates tool-tip text based on the current state. + */ + private void updateToolTipText() { + if (isChecked()) { + setToolTipText("Hide Agents which have not sent any data yet."); + } else { + setToolTipText("Show Agents which have not sent any data yet."); + } + } + } + + /** + * Double click listener for the view. + * + * @author Ivan Senic + * + */ + private class RepositoryManagerDoubleClickListener implements IDoubleClickListener { + + /** + * {@inheritDoc} + */ + @Override + public void doubleClick(final DoubleClickEvent event) { + UIJob openDataExplorerJob = new UIJob("Opening Data Explorer..") { + + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + process(); + return Status.OK_STATUS; + } + }; + openDataExplorerJob.setUser(true); + openDataExplorerJob.schedule(); + } + + /** + * Processes the double-click. + * + */ + private void process() { + RepositoryDefinition repositoryDefinition = null; + PlatformIdent platformIdent = null; + + StructuredSelection selection = (StructuredSelection) treeViewer.getSelection(); + Object firstElement = selection.getFirstElement(); + if (firstElement instanceof DeferredAgentsComposite) { + repositoryDefinition = ((DeferredAgentsComposite) firstElement).getRepositoryDefinition(); + } else if (firstElement instanceof AgentLeaf) { + platformIdent = ((AgentLeaf) firstElement).getPlatformIdent(); + Component parent = ((AgentLeaf) firstElement).getParent(); + while (null != parent) { + if (parent instanceof DeferredAgentsComposite) { + repositoryDefinition = ((DeferredAgentsComposite) parent).getRepositoryDefinition(); + break; + } + parent = parent.getParent(); + } + } + + if (null != repositoryDefinition) { + IHandlerService handlerService = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); + ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + + Command command = commandService.getCommand(ShowRepositoryHandler.COMMAND); + ExecutionEvent executionEvent = handlerService.createExecutionEvent(command, new Event()); + IEvaluationContext context = (IEvaluationContext) executionEvent.getApplicationContext(); + context.addVariable(ShowRepositoryHandler.REPOSITORY_DEFINITION, repositoryDefinition); + if (null != platformIdent) { + context.addVariable(ShowRepositoryHandler.AGENT, platformIdent); + } + + try { + command.executeWithChecks(executionEvent); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else { + if (treeViewer.getExpandedState(firstElement)) { + treeViewer.collapseToLevel(firstElement, 1); + } else { + treeViewer.expandToLevel(firstElement, 1); + } + } + } + } + + /** + * Label provider for the tree. + * + * @author Ivan Senic + * + */ + private static class RepositoryTreeLabelProvider extends StyledCellIndexLabelProvider { + + /** + * {@inheritDoc} + */ + @Override + protected Image getColumnImage(Object element, int index) { + if (element instanceof DeferredAgentsComposite) { + CmrRepositoryDefinition cmrRepositoryDefinition = (CmrRepositoryDefinition) ((DeferredAgentsComposite) element).getRepositoryDefinition(); + return ImageFormatter.getCmrRepositoryImage(cmrRepositoryDefinition, true); + } else if (element instanceof AgentLeaf) { + return ImageFormatter.getAgentImage(((AgentLeaf) element).getAgentStatusData()); + } else if (element instanceof Component) { + return ((Component) element).getImage(); + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + protected StyledString getStyledText(Object element, int index) { + if (element instanceof DeferredAgentsComposite) { + CmrRepositoryDefinition cmrRepositoryDefinition = (CmrRepositoryDefinition) ((DeferredAgentsComposite) element).getRepositoryDefinition(); + return new StyledString(cmrRepositoryDefinition.getName()); + } else if (element instanceof AgentLeaf) { + return TextFormatter.getStyledAgentLeafString((AgentLeaf) element); + } else if (element instanceof Component) { + return new StyledString(((Component) element).getName()); + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public String getToolTipText(Object element) { + if (element instanceof AgentLeaf) { + return "Double click to explore Agent in the Data Explorer"; + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public Point getToolTipShift(Object object) { + int x = 5; + int y = 5; + return new Point(x, y); + } + + /** + * {@inheritDoc} + */ + @Override + public int getToolTipDisplayDelayTime(Object object) { + return 500; + } + } + + /** + * Job for auto-update of view. + * + * @author Ivan Senic + * + */ + private final class AgentStatusUpdateJob extends Job { + + /** + * Update rate in milliseconds. Currently every 60 seconds. + */ + private static final long UPDATE_RATE = 60 * 1000L; + + /** + * Default constructor. + */ + public AgentStatusUpdateJob() { + super("Agents status auto-update"); + setUser(false); + setProperty(IProgressConstants.ICON_PROPERTY, InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_AGENT)); + schedule(UPDATE_RATE); + } + + /** + * {@inheritDoc} + */ + protected IStatus run(IProgressMonitor monitor) { + updateAgentsAndCmrStatus(); + schedule(UPDATE_RATE); + return Status.OK_STATUS; + } + + /** + * Updates the agent status for each CMR and updates the displayed CMR repository. + */ + private void updateAgentsAndCmrStatus() { + if (null != cmrPropertyForm && !cmrPropertyForm.isDisposed()) { + cmrPropertyForm.refresh(); + } + if (null != inputList) { + final List toUpdate = new ArrayList(); + for (DeferredAgentsComposite agentsComposite : inputList) { + CmrRepositoryDefinition cmrRepositoryDefinition = agentsComposite.getCmrRepositoryDefinition(); + List leafs = agentsComposite.getChildren(); + if (CollectionUtils.isNotEmpty(leafs) && cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + Map statusMap = cmrRepositoryDefinition.getGlobalDataAccessService().getAgentsOverview(); + for (Object child : leafs) { + if (child instanceof AgentLeaf) { + AgentLeaf agentLeaf = (AgentLeaf) child; + AgentStatusData agentStatusData = statusMap.get(agentLeaf.getPlatformIdent()); + agentLeaf.setAgentStatusData(agentStatusData); + toUpdate.add(agentLeaf); + } + } + } + } + if (CollectionUtils.isNotEmpty(toUpdate)) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + treeViewer.update(toUpdate.toArray(), null); + } + }); + } + + } + } + + } + + /** + * Listener that is added to the {@link TreeContentProvider} and is invoked when job of updating + * the tree elements is done. At this point this listener will re-expand all the before expanded + * elements. + * + * @author Ivan Senic + * + */ + private class ExpandFoldersUpdateCompleteListener extends JobChangeAdapter { + + /** + * {@inheritDoc} + */ + @Override + public void done(IJobChangeEvent event) { + synchronized (RepositoryManagerView.this) { + if (CollectionUtils.isNotEmpty(expandedList) && CollectionUtils.isNotEmpty(inputList)) { + for (Iterator it = expandedList.iterator(); it.hasNext();) { + Object o = it.next(); + if (treeViewer.isExpandable(o) && !treeViewer.getExpandedState(o)) { + treeViewer.expandObject(o, 1); + it.remove(); + } + } + } + } + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/view/impl/StorageManagerView.java b/inspectIT/src/info/novatec/inspectit/rcp/view/impl/StorageManagerView.java new file mode 100644 index 000000000..a39bb5e26 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/view/impl/StorageManagerView.java @@ -0,0 +1,1452 @@ +package info.novatec.inspectit.rcp.view.impl; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.action.MenuAction; +import info.novatec.inspectit.rcp.filter.FilterComposite; +import info.novatec.inspectit.rcp.form.StorageDataPropertyForm; +import info.novatec.inspectit.rcp.formatter.ImageFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.handlers.CloseAndShowStorageHandler; +import info.novatec.inspectit.rcp.handlers.ShowRepositoryHandler; +import info.novatec.inspectit.rcp.model.Component; +import info.novatec.inspectit.rcp.model.GroupedLabelsComposite; +import info.novatec.inspectit.rcp.model.storage.LocalStorageLeaf; +import info.novatec.inspectit.rcp.model.storage.LocalStorageTreeModelManager; +import info.novatec.inspectit.rcp.model.storage.StorageLeaf; +import info.novatec.inspectit.rcp.model.storage.StorageTreeModelManager; +import info.novatec.inspectit.rcp.provider.ILocalStorageDataProvider; +import info.novatec.inspectit.rcp.provider.IStorageDataProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryChangeListener; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.repository.CmrRepositoryManager; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; +import info.novatec.inspectit.rcp.repository.StorageRepositoryDefinition; +import info.novatec.inspectit.rcp.storage.InspectITStorageManager; +import info.novatec.inspectit.rcp.storage.listener.StorageChangeListener; +import info.novatec.inspectit.rcp.view.IRefreshableView; +import info.novatec.inspectit.rcp.view.tree.StorageManagerTreeContentProvider; +import info.novatec.inspectit.rcp.view.tree.StorageManagerTreeLabelProvider; +import info.novatec.inspectit.storage.IStorageData; +import info.novatec.inspectit.storage.LocalStorageData; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageData.StorageState; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.label.type.AbstractStorageLabelType; +import info.novatec.inspectit.storage.serializer.SerializationException; +import info.novatec.inspectit.util.ObjectUtils; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.IJobChangeListener; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.jface.viewers.TreeSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.jface.window.ToolTip; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.ui.ISources; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.forms.widgets.Form; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.handlers.IHandlerService; +import org.eclipse.ui.part.ViewPart; +import org.eclipse.ui.progress.UIJob; + +/** + * + * @author Ivan Senic + * + */ +public class StorageManagerView extends ViewPart implements CmrRepositoryChangeListener, StorageChangeListener, IRefreshableView { // NOPMD + + /** + * View id. + */ + public static final String VIEW_ID = "info.novatec.inspectit.rcp.view.storageManager"; + + /** + * Menu id. + */ + public static final String MENU_ID = "info.novatec.inspectit.rcp.view.storageManager.storageTree"; + + /** + * {@link CmrRepositoryManager}. + */ + private CmrRepositoryManager cmrRepositoryManager; + + /** + * {@link InspectITStorageManager}. + */ + private InspectITStorageManager storageManager; + + /** + * Map of storages and their repositories. + */ + private Map storageRepositoryMap = new ConcurrentHashMap(); + + /** + * Cashed statuses of CMR repository definitions. + */ + private ConcurrentHashMap cachedOnlineStatus = new ConcurrentHashMap(); + + /** + * Set of downloaded storages. + */ + private Set downloadedStorages = Collections.newSetFromMap(new ConcurrentHashMap()); + + /** + * Toolkit for decorations. + */ + private FormToolkit toolkit; + + /** + * Main form. + */ + private Form mainForm; + + /** + * Tree Viewer. + */ + private TreeViewer treeViewer; + + /** + * Filter for the tree. + */ + private TreeFilter treeFilter = new TreeFilter(); + + /** + * Composite for message displaying. + */ + private Composite cmrMessageComposite; + + /** + * Label type that storages are ordered by. + */ + private AbstractStorageLabelType orderingLabelType = null; + + /** + * Menu manager for filter repositories actions. Needed because it must be updated when the + * storages are added/removed. + */ + private MenuManager filterByRepositoryMenu; + + /** + * Menu manager for grouping storage by label. Needed because it must be updated when the + * storages are added/removed. + */ + private MenuManager groupByLabelMenu; + + /** + * Menu manager for filtering the storages based on the state. + */ + private MenuManager filterByStateMenu; + + /** + * Storage property form. + */ + private StorageDataPropertyForm storagePropertyForm; + + /** + * Last selected leaf. + */ + private StorageLeaf lastSelectedLeaf = null; + + /** + * Last selected local storage leaf. + */ + private LocalStorageLeaf lastSelectedLocalStorageLeaf = null; + + /** + * Boolean for layout of view. + */ + private boolean verticaLayout = true; + + /** + * Views main composite. + */ + private SashForm mainComposite; + + /** + * Upper composite where filter box and storage tree is located. + */ + private Composite upperComposite; + + /** + * Filter storages composite that will be displayed at top of view. + */ + private FilterStorageComposite filterStorageComposite; + + /** + * Selection button for showing the remove storages. + */ + private Button remoteStorageSelection; + + /** + * Selection button for showing the local storages. + */ + private Button localStorageSelection; + + /** + * Default constructor. + */ + public StorageManagerView() { + cmrRepositoryManager = InspectIT.getDefault().getCmrRepositoryManager(); + cmrRepositoryManager.addCmrRepositoryChangeListener(this); + storageManager = InspectIT.getDefault().getInspectITStorageManager(); + storageManager.addStorageChangeListener(this); + updateStorageList(null); + updateDownloadedStorages(); + } + + /** + * {@inheritDoc} + */ + @Override + public void createPartControl(Composite parent) { + toolkit = new FormToolkit(parent.getDisplay()); + createViewToolbar(); + + mainComposite = new SashForm(parent, SWT.VERTICAL); + GridLayout mainLayout = new GridLayout(1, true); + mainLayout.marginWidth = 0; + mainLayout.marginHeight = 0; + mainComposite.setLayout(mainLayout); + + mainForm = toolkit.createForm(mainComposite); + mainForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + GridLayout lauout = new GridLayout(1, true); + lauout.marginWidth = 0; + lauout.marginHeight = 0; + mainForm.getBody().setLayout(lauout); + toolkit.decorateFormHeading(mainForm); + createHeadClient(); + + upperComposite = toolkit.createComposite(mainForm.getBody()); + lauout = new GridLayout(1, true); + lauout.marginHeight = 0; + upperComposite.setLayout(lauout); + upperComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + Tree tree = toolkit.createTree(upperComposite, SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI); + treeViewer = new TreeViewer(tree); + treeViewer.setContentProvider(new StorageManagerTreeContentProvider()); + treeViewer.setLabelProvider(new StorageManagerTreeLabelProvider()); + // treeViewer.setComparator(new ServerViewComparator()); + treeViewer.setComparator(new ViewerComparator() { + @Override + public int compare(Viewer viewer, Object e1, Object e2) { + if (e1 instanceof GroupedLabelsComposite && e2 instanceof GroupedLabelsComposite) { + return ObjectUtils.compare((GroupedLabelsComposite) e1, (GroupedLabelsComposite) e2); + } else if (e1 instanceof Component && e2 instanceof Component) { + return ((Component) e1).getName().compareToIgnoreCase(((Component) e2).getName()); + } + return super.compare(viewer, e1, e2); + } + }); + treeViewer.addFilter(treeFilter); + treeViewer.addFilter(filterStorageComposite.getFilter()); + treeViewer.getTree().setVisible(false); + ColumnViewerToolTipSupport.enableFor(treeViewer, ToolTip.NO_RECREATE); + + storagePropertyForm = new StorageDataPropertyForm(mainComposite); + storagePropertyForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + treeViewer.addSelectionChangedListener(storagePropertyForm); + + treeViewer.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + StructuredSelection structuredSelection = (StructuredSelection) event.getSelection(); + if (structuredSelection.getFirstElement() instanceof StorageLeaf) { + lastSelectedLeaf = (StorageLeaf) structuredSelection.getFirstElement(); + } else if (structuredSelection.getFirstElement() instanceof LocalStorageLeaf) { + lastSelectedLocalStorageLeaf = (LocalStorageLeaf) structuredSelection.getFirstElement(); + } + } + }); + + treeViewer.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + updateViewToolbar(); + } + }); + + treeViewer.addDoubleClickListener(new DoubleClickListener()); + + MenuManager menuManager = new MenuManager(); + menuManager.setRemoveAllWhenShown(true); + getSite().registerContextMenu(MENU_ID, menuManager, treeViewer); + Control control = treeViewer.getControl(); + Menu menu = menuManager.createContextMenu(control); + control.setMenu(menu); + + mainComposite.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + int width = mainComposite.getBounds().width; + int height = mainComposite.getBounds().height; + + if (width > height && verticaLayout) { + verticaLayout = false; + mainComposite.setOrientation(SWT.HORIZONTAL); + } else if (width < height && !verticaLayout) { + verticaLayout = true; + mainComposite.setOrientation(SWT.VERTICAL); + } + + mainComposite.layout(); + } + }); + + updateFormBody(); + updateViewToolbar(); + + mainComposite.setWeights(new int[] { 2, 3 }); + getSite().setSelectionProvider(treeViewer); + } + + /** + * Creates the head client for form. + */ + private void createHeadClient() { + Composite headClient = new Composite(mainForm.getHead(), SWT.NONE); + GridLayout gl = new GridLayout(3, false); + gl.marginHeight = 0; + gl.marginWidth = 0; + headClient.setLayout(gl); + + new Label(headClient, SWT.NONE).setText("Show available:"); + + remoteStorageSelection = new Button(headClient, SWT.RADIO); + remoteStorageSelection.setText("Online"); + remoteStorageSelection.setSelection(true); + + localStorageSelection = new Button(headClient, SWT.RADIO); + localStorageSelection.setText("Local"); + + remoteStorageSelection.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + updateViewToolbar(); + updateFormBody(); + } + }); + + // filter composite + filterStorageComposite = new FilterStorageComposite(headClient, SWT.NONE); + filterStorageComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1)); + + mainForm.setHeadClient(headClient); + } + + /** + * Creates the view tool-bar. + */ + private void createViewToolbar() { + IToolBarManager toolBarManager = getViewSite().getActionBars().getToolBarManager(); + toolBarManager.add(new ShowPropertiesAction()); + + MenuAction filterMenuAction = new MenuAction(); + filterMenuAction.setText("Group and Filter"); + filterMenuAction.setImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_FILTER)); + + groupByLabelMenu = new MenuManager("Group Storages By"); + filterMenuAction.addContributionItem(groupByLabelMenu); + + filterByRepositoryMenu = new MenuManager("Filter By Repository"); + filterMenuAction.addContributionItem(filterByRepositoryMenu); + + filterByStateMenu = new MenuManager("Filter By Storage State"); + filterByStateMenu.add(new FilterStatesAction("Writable", StorageState.OPENED)); + filterByStateMenu.add(new FilterStatesAction("Recording", StorageState.RECORDING)); + filterByStateMenu.add(new FilterStatesAction("Readable", StorageState.CLOSED)); + filterMenuAction.addContributionItem(filterByStateMenu); + + toolBarManager.add(filterMenuAction); + toolBarManager.add(new Separator()); + + } + + /** + * Updates the storage list for all {@link CmrRepositoryDefinition}. + * + * @param jobListener + * the listener. + */ + private void updateStorageList(IJobChangeListener jobListener) { + Job updateStorageListJob = new Job("Update Storages") { + @Override + protected IStatus run(IProgressMonitor monitor) { + storageRepositoryMap.clear(); + for (CmrRepositoryDefinition cmrRepositoryDefinition : cmrRepositoryManager.getCmrRepositoryDefinitions()) { + boolean canUpdate = false; + if (cmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.ONLINE) { + canUpdate = true; + } else { + OnlineStatus cachedStatus = cachedOnlineStatus.get(cmrRepositoryDefinition); + if (OnlineStatus.ONLINE.equals(cachedStatus)) { + canUpdate = true; + } + } + if (canUpdate) { + try { + List storages = cmrRepositoryDefinition.getStorageService().getExistingStorages(); + for (StorageData storage : storages) { + storageRepositoryMap.put(storage, cmrRepositoryDefinition); + } + } catch (Exception e) { + continue; + } + } + } + return Status.OK_STATUS; + } + }; + if (null != jobListener) { + updateStorageListJob.addJobChangeListener(jobListener); + } + updateStorageListJob.schedule(); + } + + /** + * Updates the storage list only for provided {@link CmrRepositoryDefinition}. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} + * @param removeOnly + * If set to true, no storages will be loaded from the CMR. + * @param jobListener + * the job listener. + */ + private void updateStorageList(final CmrRepositoryDefinition cmrRepositoryDefinition, final boolean removeOnly, IJobChangeListener jobListener) { + Job updateStorageListJob = new Job("Updating Storages") { + @Override + protected IStatus run(IProgressMonitor monitor) { + while (storageRepositoryMap.values().remove(cmrRepositoryDefinition)) { + continue; + } + if (!removeOnly) { + boolean canUpdate = false; + if (cmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.ONLINE) { + canUpdate = true; + } else { + OnlineStatus cachedStatus = cachedOnlineStatus.get(cmrRepositoryDefinition); + if (OnlineStatus.ONLINE.equals(cachedStatus)) { + canUpdate = true; + } + } + if (canUpdate) { + List storages = cmrRepositoryDefinition.getStorageService().getExistingStorages(); + for (StorageData storage : storages) { + storageRepositoryMap.put(storage, cmrRepositoryDefinition); + } + } + } + return Status.OK_STATUS; + } + }; + if (null != jobListener) { + updateStorageListJob.addJobChangeListener(jobListener); + } + updateStorageListJob.schedule(); + } + + /** + * Updates the list of downloaded storages. + */ + private void updateDownloadedStorages() { + downloadedStorages.clear(); + downloadedStorages.addAll(InspectIT.getDefault().getInspectITStorageManager().getDownloadedStorages()); + } + + /** + * Updates the form body. + */ + private void updateFormBody() { + clearFormBody(); + if (remoteStorageSelection.getSelection()) { + if (!storageRepositoryMap.isEmpty()) { + treeViewer.getTree().setVisible(true); + treeViewer.getTree().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + treeViewer.setInput(new StorageTreeModelManager(storageRepositoryMap, orderingLabelType)); + treeViewer.expandToLevel(TreeViewer.ALL_LEVELS); + if (null != lastSelectedLeaf && storageRepositoryMap.keySet().contains(lastSelectedLeaf.getStorageData())) { + StructuredSelection ss = new StructuredSelection(lastSelectedLeaf); + treeViewer.setSelection(ss, true); + } + filterStorageComposite.setEnabled(true); + } else { + displayMessage("No storage information available on currently available CMR repositories.", Display.getDefault().getSystemImage(SWT.ICON_INFORMATION)); + filterStorageComposite.setEnabled(false); + } + } else { + if (!downloadedStorages.isEmpty()) { + treeViewer.getTree().setVisible(true); + treeViewer.getTree().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + treeViewer.setInput(new LocalStorageTreeModelManager(downloadedStorages, orderingLabelType)); + treeViewer.expandToLevel(TreeViewer.ALL_LEVELS); + if (null != lastSelectedLocalStorageLeaf && downloadedStorages.contains(lastSelectedLocalStorageLeaf.getLocalStorageData())) { + StructuredSelection ss = new StructuredSelection(lastSelectedLocalStorageLeaf); + treeViewer.setSelection(ss, true); + } + filterStorageComposite.setEnabled(true); + } else { + displayMessage("No downloaded storage is available on the local machine.", Display.getDefault().getSystemImage(SWT.ICON_INFORMATION)); + filterStorageComposite.setEnabled(false); + } + } + upperComposite.layout(); + } + + /** + * Clears the look of the forms body. + */ + private void clearFormBody() { + if (cmrMessageComposite != null && !cmrMessageComposite.isDisposed()) { + cmrMessageComposite.dispose(); + } + treeViewer.setInput(Collections.emptyList()); + treeViewer.getTree().setVisible(false); + treeViewer.getTree().setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + } + + /** + * Displays the message on the provided composite. + * + * @param text + * Text of message. + * @param image + * Image to show. + */ + private void displayMessage(String text, Image image) { + if (null == cmrMessageComposite || cmrMessageComposite.isDisposed()) { + cmrMessageComposite = toolkit.createComposite(upperComposite); + } else { + for (Control c : cmrMessageComposite.getChildren()) { + if (!c.isDisposed()) { + c.dispose(); + } + } + } + cmrMessageComposite.setLayout(new GridLayout(2, false)); + cmrMessageComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + toolkit.createLabel(cmrMessageComposite, null).setImage(image); + toolkit.createLabel(cmrMessageComposite, text, SWT.WRAP).setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); + } + + /** + * Updates the view tool-bar. + */ + private void updateViewToolbar() { + boolean remoteStoragesShown = remoteStorageSelection.getSelection(); + + // filter by repository visible only when remote storages are displayed + filterByRepositoryMenu.removeAll(); + for (CmrRepositoryDefinition cmrRepositoryDefinition : cmrRepositoryManager.getCmrRepositoryDefinitions()) { + filterByRepositoryMenu.add(new FilterRepositoriesAction(cmrRepositoryDefinition)); + } + filterByRepositoryMenu.getParent().update(false); + filterByRepositoryMenu.setVisible(remoteStoragesShown); + + // filter by state is not visible with downloaded storages displayed + filterByStateMenu.setVisible(remoteStoragesShown); + + // group by label + Set> availableLabelTypes = new HashSet>(); + if (remoteStoragesShown) { + for (StorageData storageData : storageRepositoryMap.keySet()) { + for (AbstractStorageLabel label : storageData.getLabelList()) { + availableLabelTypes.add(label.getStorageLabelType()); + } + } + } else { + for (LocalStorageData localStorageData : downloadedStorages) { + for (AbstractStorageLabel label : localStorageData.getLabelList()) { + availableLabelTypes.add(label.getStorageLabelType()); + } + } + } + + groupByLabelMenu.removeAll(); + if (remoteStoragesShown) { + groupByLabelMenu.add(new LabelOrderAction("CMR Repository", InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_SERVER_ONLINE_SMALL), null, null == orderingLabelType)); + } else { + groupByLabelMenu.add(new LabelOrderAction("None", InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_STORAGE_DOWNLOADED), null, null == orderingLabelType)); + } + for (AbstractStorageLabelType labelType : availableLabelTypes) { + if (labelType.isGroupingEnabled()) { + groupByLabelMenu.add(new LabelOrderAction(TextFormatter.getLabelName(labelType), ImageFormatter.getImageDescriptorForLabel(labelType), labelType, ObjectUtils.equals(labelType, + orderingLabelType))); + } + } + + } + + /** + * Performs update. + * + * @param updateStorageList + * If the update should go to the CMRs for an updated storage list. + */ + private void performUpdate(final boolean updateStorageList) { + updateDownloadedStorages(); + if (updateStorageList) { + updateStorageList(new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + mainForm.setBusy(true); + updateFormBody(); + updateViewToolbar(); + mainForm.setBusy(false); + mainForm.layout(); + } + }); + } + }); + } else { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + mainForm.setBusy(true); + updateFormBody(); + updateViewToolbar(); + mainForm.setBusy(false); + mainForm.layout(); + } + }); + } + + } + + /** + * {@inheritDoc} + */ + @Override + public void setFocus() { + if (treeViewer.getTree().isVisible()) { + treeViewer.getTree().setFocus(); + } else { + mainForm.setFocus(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryOnlineStatusUpdated(CmrRepositoryDefinition repositoryDefinition, OnlineStatus oldStatus, OnlineStatus newStatus) { + if (newStatus == OnlineStatus.ONLINE) { + OnlineStatus cachedStatus = cachedOnlineStatus.get(repositoryDefinition); + if (null == cachedStatus || OnlineStatus.OFFLINE.equals(cachedStatus) || OnlineStatus.UNKNOWN.equals(cachedStatus)) { + updateStorageList(repositoryDefinition, false, new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + updateFormBody(); + } + }); + } + }); + + } + cachedOnlineStatus.put(repositoryDefinition, newStatus); + } else if (newStatus == OnlineStatus.OFFLINE) { + OnlineStatus cachedStatus = cachedOnlineStatus.get(repositoryDefinition); + if (null == cachedStatus || OnlineStatus.ONLINE.equals(cachedStatus)) { + updateStorageList(repositoryDefinition, true, new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + updateFormBody(); + } + }); + } + }); + + } + cachedOnlineStatus.put(repositoryDefinition, newStatus); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryAdded(CmrRepositoryDefinition cmrRepositoryDefinition) { + cachedOnlineStatus.put(cmrRepositoryDefinition, cmrRepositoryDefinition.getOnlineStatus()); + updateStorageList(cmrRepositoryDefinition, false, new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + performUpdate(false); + } + + }); + + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryRemoved(CmrRepositoryDefinition cmrRepositoryDefinition) { + cachedOnlineStatus.remove(cmrRepositoryDefinition); + updateStorageList(cmrRepositoryDefinition, true, new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + performUpdate(false); + } + + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryDataUpdated(CmrRepositoryDefinition cmrRepositoryDefinition) { + Display.getDefault().asyncExec(new Runnable() { + + @Override + public void run() { + updateFormBody(); + updateViewToolbar(); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void repositoryAgentDeleted(CmrRepositoryDefinition cmrRepositoryDefinition, PlatformIdent agent) { + } + + /** + * {@inheritDoc} + */ + public void refresh() { + performUpdate(true); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canRefresh() { + return !storageRepositoryMap.isEmpty() || !cmrRepositoryManager.getCmrRepositoryDefinitions().isEmpty(); + } + + /** + * Refreshes the view, only by refreshing the storages on the given repository. + * + * @param cmrRepositoryDefinition + * Repository to update storages for. + */ + public void refresh(CmrRepositoryDefinition cmrRepositoryDefinition) { + updateStorageList(cmrRepositoryDefinition, false, new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + performUpdate(false); + } + + }); + } + + /** + * Show or hides properties. + * + * @param show + * Should properties be shown. + */ + public void setShowProperties(boolean show) { + if (show) { + StructuredSelection selection = (StructuredSelection) treeViewer.getSelection(); + if (!selection.isEmpty()) { + if (selection.getFirstElement() instanceof StorageLeaf) { + StorageLeaf storageLeaf = ((StorageLeaf) selection.getFirstElement()); + storagePropertyForm = new StorageDataPropertyForm(mainComposite, storageLeaf); + storagePropertyForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + } else if (selection.getFirstElement() instanceof LocalStorageLeaf) { + IStorageData storageData = ((LocalStorageLeaf) selection.getFirstElement()).getLocalStorageData(); + storagePropertyForm = new StorageDataPropertyForm(mainComposite, null, storageData); + storagePropertyForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + } else { + storagePropertyForm = new StorageDataPropertyForm(mainComposite); + storagePropertyForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + } + } + + treeViewer.addSelectionChangedListener(storagePropertyForm); + mainComposite.setWeights(new int[] { 2, 3 }); + mainComposite.layout(); + } else { + if (null != storagePropertyForm && !storagePropertyForm.isDisposed()) { + treeViewer.removeSelectionChangedListener(storagePropertyForm); + storagePropertyForm.dispose(); + storagePropertyForm = null; // NOPMD + } + mainComposite.setWeights(new int[] { 1 }); + mainComposite.layout(); + } + } + + /** + * Performs update of the view, without getting data from CMR. + */ + public void refreshWithoutCmrCall() { + performUpdate(false); + } + + /** + * {@inheritDoc} + */ + @Override + public void storageDataUpdated(IStorageData storageData) { + CmrRepositoryDefinition repositoryToUpdate = storageRepositoryMap.get(storageData); + if (null != repositoryToUpdate) { + refresh(repositoryToUpdate); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void storageRemotelyDeleted(IStorageData storageData) { + for (Iterator> it = storageRepositoryMap.entrySet().iterator(); it.hasNext();) { + if (Objects.equals(it.next().getKey().getId(), storageData.getId())) { + it.remove(); + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + if (remoteStorageSelection.getSelection()) { + refreshWithoutCmrCall(); + } + } + }); + + break; + } + + } + } + + /** + * {@inheritDoc} + */ + @Override + public void storageLocallyDeleted(IStorageData storageData) { + for (Iterator it = downloadedStorages.iterator(); it.hasNext();) { + if (Objects.equals(it.next().getId(), storageData.getId())) { + it.remove(); + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + if (localStorageSelection.getSelection()) { + refreshWithoutCmrCall(); + } + } + }); + break; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() { + cmrRepositoryManager.removeCmrRepositoryChangeListener(this); + storageManager.removeStorageChangeListener(this); + super.dispose(); + } + + /** + * Filter for the tree. + * + * @author Ivan Senic + * + */ + private static class TreeFilter extends ViewerFilter { + + /** + * Set of excluded repositories. + */ + private Set filteredRespositories = new HashSet(); + + /** + * Set of excluded states. + */ + private Set filteredStates = new HashSet(); + + /** + * {@inheritDoc} + */ + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + if (element instanceof StorageLeaf) { + StorageLeaf storageLeaf = (StorageLeaf) element; + if (filteredRespositories.contains(storageLeaf.getCmrRepositoryDefinition())) { + return false; + } + if (filteredStates.contains(storageLeaf.getStorageData().getState())) { + return false; + } + } + return true; + } + + /** + * @return the filteredRespositories + */ + public Set getFilteredRespositories() { + return filteredRespositories; + } + + /** + * @return the filteredStates + */ + public Set getFilteredStates() { + return filteredStates; + } + + } + + /** + * Action for selecting the grouping of storages. + * + * @author Ivan Senic + * + */ + private class LabelOrderAction extends Action { + + /** + * Label type to group. + */ + private AbstractStorageLabelType labelType; + + /** + * Constructor. + * + * @param name + * Name of action. + * @param imgDescriptor + * {@link ImageDescriptor}. + * @param labelType + * Label type to represent. Null for default settings. + * @param isChecked + * Should be checked. + */ + public LabelOrderAction(String name, ImageDescriptor imgDescriptor, AbstractStorageLabelType labelType, boolean isChecked) { + super(name, Action.AS_RADIO_BUTTON); + this.labelType = labelType; + setChecked(isChecked); + setImageDescriptor(imgDescriptor); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + if (isChecked()) { + orderingLabelType = labelType; + updateFormBody(); + } + } + } + + /** + * Filter by storage repository action. + * + * @author Ivan Senic + * + */ + private class FilterRepositoriesAction extends Action { + + /** + * Cmr to exclude/include. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * @param cmrRepositoryDefinition + * Cmr to exclude/include. + */ + public FilterRepositoriesAction(CmrRepositoryDefinition cmrRepositoryDefinition) { + super(); + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + setText(cmrRepositoryDefinition.getName()); + setChecked(!treeFilter.getFilteredRespositories().contains(cmrRepositoryDefinition)); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + if (isChecked()) { + treeFilter.getFilteredRespositories().remove(cmrRepositoryDefinition); + } else { + treeFilter.getFilteredRespositories().add(cmrRepositoryDefinition); + } + treeViewer.refresh(); + treeViewer.expandToLevel(TreeViewer.ALL_LEVELS); + if (null != lastSelectedLeaf && storageRepositoryMap.keySet().contains(lastSelectedLeaf.getStorageData())) { + StructuredSelection ss = new StructuredSelection(lastSelectedLeaf); + treeViewer.setSelection(ss, true); + } + } + + } + + /** + * Filter by storage state action. + * + * @author Ivan Senic + * + */ + private class FilterStatesAction extends Action { + + /** + * Storage state to exclude/include. + */ + private StorageState state; + + /** + * + * @param text + * Action text. + * @param state + * Storage state to exclude/include. + */ + public FilterStatesAction(String text, StorageState state) { + super(); + this.state = state; + setText(text); + setChecked(!treeFilter.getFilteredStates().contains(state)); + } + + /** + * {@inheritDoc} + */ + @Override + public void run() { + if (isChecked()) { + treeFilter.getFilteredStates().remove(state); + } else { + treeFilter.getFilteredStates().add(state); + } + treeViewer.refresh(); + treeViewer.expandToLevel(TreeViewer.ALL_LEVELS); + if (null != lastSelectedLeaf && storageRepositoryMap.keySet().contains(lastSelectedLeaf.getStorageData())) { + StructuredSelection ss = new StructuredSelection(lastSelectedLeaf); + treeViewer.setSelection(ss, true); + } + } + + } + + /** + * Action for show hide properties. + * + * @author Ivan Senic + * + */ + private class ShowPropertiesAction extends Action { + + /** + * Default constructor. + */ + public ShowPropertiesAction() { + super(null, AS_CHECK_BOX); + setImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_PROPERTIES)); + setChecked(true); + setToolTipText("Hide Properties"); + } + + /** + * {@inheritDoc} + */ + public void run() { + if (isChecked()) { + setShowProperties(true); + setToolTipText("Hide Properties"); + } else { + setShowProperties(false); + setToolTipText("Show Properties"); + } + }; + } + + /** + * + * @author Ivan Senic + * + */ + private final class FilterStorageComposite extends FilterComposite { + + /** + * String to be filtered. + */ + private String filterString = ""; + + /** + * Filter. + */ + private ViewerFilter filter = new ViewerFilter() { + + /** + * {@inheritDoc} + */ + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + if (Objects.equals("", filterString)) { + return true; + } else { + if (element instanceof IStorageDataProvider) { + return select(((IStorageDataProvider) element).getStorageData()); + } else if (element instanceof ILocalStorageDataProvider) { + return select(((ILocalStorageDataProvider) element).getLocalStorageData()); + } + return true; + } + } + + /** + * Does a filter select on {@link StorageData}. + * + * @param storageData + * {@link IStorageData} + * @return True if data in {@link IStorageData} fits the filter string. + */ + private boolean select(IStorageData storageData) { + if (StringUtils.containsIgnoreCase(storageData.getName(), filterString)) { + return true; + } + if (StringUtils.containsIgnoreCase(storageData.getDescription(), filterString)) { + return true; + } + for (AbstractStorageLabel label : storageData.getLabelList()) { + if (StringUtils.containsIgnoreCase(TextFormatter.getLabelValue(label, false), filterString)) { + return true; + } + } + + if (storageData instanceof StorageData) { + if (StringUtils.containsIgnoreCase(((StorageData) storageData).getState().toString(), filterString)) { + return true; + } + } + + return false; + } + + }; + + /** + * Default constructor. + * + * @param parent + * A widget which will be the parent of the new instance (cannot be null). + * @param style + * The style of widget to construct. + * @see Composite#Composite(Composite, int) + */ + public FilterStorageComposite(Composite parent, int style) { + super(parent, style, "Filter storages"); + ((GridLayout) getLayout()).marginWidth = 0; + } + + /** + * {@inheritDoc} + */ + @Override + protected void executeCancel() { + this.filterString = ""; + treeViewer.refresh(); + treeViewer.expandToLevel(TreeViewer.ALL_LEVELS); + if (remoteStorageSelection.getSelection()) { + if (null != lastSelectedLeaf && storageRepositoryMap.keySet().contains(lastSelectedLeaf.getStorageData())) { + StructuredSelection ss = new StructuredSelection(lastSelectedLeaf); + treeViewer.setSelection(ss, true); + } + } else { + if (null != lastSelectedLocalStorageLeaf && downloadedStorages.contains(lastSelectedLocalStorageLeaf.getLocalStorageData())) { + StructuredSelection ss = new StructuredSelection(lastSelectedLocalStorageLeaf); + treeViewer.setSelection(ss, true); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void executeFilter(String filterString) { + this.filterString = filterString; + treeViewer.refresh(); + treeViewer.expandToLevel(TreeViewer.ALL_LEVELS); + if (remoteStorageSelection.getSelection()) { + if (null != lastSelectedLeaf && storageRepositoryMap.keySet().contains(lastSelectedLeaf.getStorageData())) { + StructuredSelection ss = new StructuredSelection(lastSelectedLeaf); + treeViewer.setSelection(ss, true); + } + } else { + if (null != lastSelectedLocalStorageLeaf && downloadedStorages.contains(lastSelectedLocalStorageLeaf.getLocalStorageData())) { + StructuredSelection ss = new StructuredSelection(lastSelectedLocalStorageLeaf); + treeViewer.setSelection(ss, true); + } + } + } + + /** + * Gets {@link #filter}. + * + * @return {@link #filter} + */ + public ViewerFilter getFilter() { + return filter; + } + + } + + /** + * Double click listener, that opens the data explorer. + * + * @author Ivan Senic + * + */ + private class DoubleClickListener implements IDoubleClickListener { + + /** + * {@inheritDoc} + */ + public void doubleClick(final DoubleClickEvent event) { + UIJob openDataExplorerJob = new UIJob("Opening Data Explorer..") { + + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + process(); + return Status.OK_STATUS; + } + }; + openDataExplorerJob.setUser(true); + openDataExplorerJob.schedule(); + } + + /** + * Processes the double-click. + */ + private void process() { + StructuredSelection selection = (StructuredSelection) treeViewer.getSelection(); + if (selection.getFirstElement() instanceof IStorageDataProvider) { + showStorage((IStorageDataProvider) selection.getFirstElement(), InspectIT.getDefault().getInspectITStorageManager()); + } else if (selection.getFirstElement() instanceof ILocalStorageDataProvider) { + showStorage((ILocalStorageDataProvider) selection.getFirstElement(), InspectIT.getDefault().getInspectITStorageManager()); + } else { + TreeSelection treeSelection = (TreeSelection) selection; + TreePath path = treeSelection.getPaths()[0]; + if (null != path) { + boolean expanded = treeViewer.getExpandedState(path); + if (expanded) { + treeViewer.collapseToLevel(path, 1); + } else { + treeViewer.expandToLevel(path, 1); + } + } + } + } + + /** + * Executes show repository command. + * + * @param repositoryDefinition + * Repository to open. + */ + private void executeShowRepositoryCommand(RepositoryDefinition repositoryDefinition) { + try { + IHandlerService handlerService = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); + ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + + Command command = commandService.getCommand(ShowRepositoryHandler.COMMAND); + ExecutionEvent executionEvent = handlerService.createExecutionEvent(command, new Event()); + IEvaluationContext context = (IEvaluationContext) executionEvent.getApplicationContext(); + context.addVariable(ShowRepositoryHandler.REPOSITORY_DEFINITION, repositoryDefinition); + + command.executeWithChecks(executionEvent); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Shows storage from the {@link IStorageDataProvider}. + * + * @param storageDataProvider + * {@link IStorageDataProvider} + * @param storageManager + * {@link InspectITStorageManager} + */ + private void showStorage(IStorageDataProvider storageDataProvider, final InspectITStorageManager storageManager) { + final StorageData storageData = storageDataProvider.getStorageData(); + final CmrRepositoryDefinition cmrRepositoryDefinition = storageDataProvider.getCmrRepositoryDefinition(); + try { + if (storageManager.isStorageMounted(storageData)) { + // if we already have all data needed, get the repository definition and show it + LocalStorageData localStorageData = storageManager.getLocalDataForStorage(storageData); + RepositoryDefinition repositoryDefinition = storageManager.getStorageRepositoryDefinition(localStorageData); + executeShowRepositoryCommand(repositoryDefinition); + } else if (storageData.getState() == StorageState.CLOSED) { + // if it is closed, mount it first + PlatformUI.getWorkbench().getProgressService().busyCursorWhile(new IRunnableWithProgress() { + @Override + public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + try { + SubMonitor subMonitor = SubMonitor.convert(monitor); + storageManager.mountStorage(storageData, cmrRepositoryDefinition, subMonitor); + monitor.done(); + } catch (Exception e) { + throw new InvocationTargetException(e); + } + } + }); + LocalStorageData localStorageData = storageManager.getLocalDataForStorage(storageData); + RepositoryDefinition repositoryDefinition = storageManager.getStorageRepositoryDefinition(localStorageData); + executeShowRepositoryCommand(repositoryDefinition); + } else if (storageData.getState() == StorageState.OPENED) { + // if it's in writable state offer user to finalize it and explore it + String dialogMessage = "Storages that are in writable mode can not be explored. Do you want to finalize selected storage first and then open it?"; + MessageDialog dialog = new MessageDialog(getSite().getShell(), "Opening Writable Storage", null, dialogMessage, MessageDialog.QUESTION, new String[] { "Yes", "No" }, 0); + if (0 == dialog.open()) { + treeViewer.setSelection(treeViewer.getSelection()); + IHandlerService handlerService = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); + ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + + Command command = commandService.getCommand(CloseAndShowStorageHandler.COMMAND); + ExecutionEvent executionEvent = handlerService.createExecutionEvent(command, new Event()); + IEvaluationContext context = (IEvaluationContext) executionEvent.getApplicationContext(); + context.addVariable(CloseAndShowStorageHandler.STORAGE_DATA_PROVIDER, storageDataProvider); + context.addVariable(ISources.ACTIVE_SITE_NAME, getSite()); + try { + command.executeWithChecks(executionEvent); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return; + } else if (storageData.getState() == StorageState.RECORDING) { + // if it is used for recording, just show message + InspectIT.getDefault().createInfoDialog("Selected storage is currently used for recording, it can not be explored.", -1); + } + } catch (InvocationTargetException | InterruptedException e) { // NOPMD + InspectIT.getDefault().createErrorDialog("Exception occurred trying to mount the storage", e, -1); + } catch (SerializationException e) { + String msg = "Data in the remote storage " + storageData + " can not be read with this version of inspectIT."; + if (null != storageData.getCmrVersion()) { + msg += " Version of the CMR where storage was created is " + storageData.getCmrVersion() + "."; + } else { + msg += " Version of the CMR where storage was created is unknown"; + } + InspectIT.getDefault().createErrorDialog(msg, e, -1); + } catch (StorageException | IOException e) { + InspectIT.getDefault().createErrorDialog("Exception occurred trying to display the remote storage", e, -1); + } + } + + /** + * Shows storage from the {@link ILocalStorageDataProvider}. + * + * @param localStorageDataProvider + * {@link ILocalStorageDataProvider} + * @param storageManager + * {@link InspectITStorageManager} + */ + private void showStorage(ILocalStorageDataProvider localStorageDataProvider, InspectITStorageManager storageManager) { + LocalStorageData localStorageData = localStorageDataProvider.getLocalStorageData(); + try { + if (localStorageData.isFullyDownloaded()) { + StorageRepositoryDefinition storageRepositoryDefinition; + storageRepositoryDefinition = storageManager.getStorageRepositoryDefinition(localStorageData); + executeShowRepositoryCommand(storageRepositoryDefinition); + } + } catch (SerializationException e) { + String msg = "Data in the remote storage " + localStorageData + " can not be read with this version of inspectIT."; + if (null != localStorageData.getCmrVersion()) { + msg += " Version of the CMR where storage was created is " + localStorageData.getCmrVersion() + "."; + } else { + msg += " Version of the CMR where storage was created is unknown"; + } + InspectIT.getDefault().createErrorDialog(msg, e, -1); + } catch (StorageException | IOException e) { + InspectIT.getDefault().createErrorDialog("Exception occurred trying to display the downloaded storage", e, -1); + } + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/view/listener/TreeViewDoubleClickListener.java b/inspectIT/src/info/novatec/inspectit/rcp/view/listener/TreeViewDoubleClickListener.java new file mode 100644 index 000000000..a79a61ddf --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/view/listener/TreeViewDoubleClickListener.java @@ -0,0 +1,64 @@ +package info.novatec.inspectit.rcp.view.listener; + +import info.novatec.inspectit.rcp.handlers.OpenViewHandler; +import info.novatec.inspectit.rcp.model.Component; + +import org.eclipse.core.commands.Command; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.expressions.IEvaluationContext; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.jface.viewers.TreeSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.swt.widgets.Event; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.commands.ICommandService; +import org.eclipse.ui.handlers.IHandlerService; + +/** + * Double click listener for the explorers trees. + * + * @author Ivan Senic + * + */ +public class TreeViewDoubleClickListener implements IDoubleClickListener { + + /** + * {@inheritDoc} + */ + @Override + public void doubleClick(DoubleClickEvent event) { + TreeSelection selection = (TreeSelection) event.getSelection(); + Object element = selection.getFirstElement(); + if (null != element) { + if (((Component) element).getInputDefinition() == null) { + TreeViewer treeViewer = (TreeViewer) event.getViewer(); + TreePath path = selection.getPaths()[0]; + if (null != path) { + boolean expanded = treeViewer.getExpandedState(path); + if (expanded) { + treeViewer.collapseToLevel(path, 1); + } else { + treeViewer.expandToLevel(path, 1); + } + } + } else { + IHandlerService handlerService = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); + ICommandService commandService = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); + + Command command = commandService.getCommand(OpenViewHandler.COMMAND); + ExecutionEvent executionEvent = handlerService.createExecutionEvent(command, new Event()); + IEvaluationContext context = (IEvaluationContext) executionEvent.getApplicationContext(); + context.addVariable(OpenViewHandler.INPUT, ((Component) element).getInputDefinition()); + + try { + command.executeWithChecks(executionEvent); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/view/tree/StorageManagerTreeContentProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/view/tree/StorageManagerTreeContentProvider.java new file mode 100644 index 000000000..5af2926e2 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/view/tree/StorageManagerTreeContentProvider.java @@ -0,0 +1,75 @@ +package info.novatec.inspectit.rcp.view.tree; + +import info.novatec.inspectit.rcp.model.Component; +import info.novatec.inspectit.rcp.model.Composite; +import info.novatec.inspectit.rcp.model.storage.LocalStorageTreeModelManager; +import info.novatec.inspectit.rcp.model.storage.StorageTreeModelManager; + +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; + +/** + * Content provider for the storage tree. + * + * @author Ivan Senic + * + */ +public class StorageManagerTreeContentProvider extends ArrayContentProvider implements ITreeContentProvider { + + /** + * {@inheritDoc} + */ + public void dispose() { + } + + /** + * {@inheritDoc} + */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + /** + * {@inheritDoc} + */ + public Object[] getElements(Object inputElement) { + if (inputElement instanceof StorageTreeModelManager) { + return ((StorageTreeModelManager) inputElement).getRootObjects(); + } else if (inputElement instanceof LocalStorageTreeModelManager) { + return ((LocalStorageTreeModelManager) inputElement).getRootObjects(); + } else { + return super.getElements(inputElement); + } + } + + /** + * {@inheritDoc} + */ + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof Composite) { + return ((Composite) parentElement).getChildren().toArray(); + } + return new Object[0]; + } + + /** + * {@inheritDoc} + */ + public Object getParent(Object element) { + if (element instanceof Component) { + return ((Component) element).getParent(); + } + return null; + } + + /** + * {@inheritDoc} + */ + public boolean hasChildren(Object element) { + if (element instanceof Composite) { + return !((Composite) element).getChildren().isEmpty(); + } + return false; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/view/tree/StorageManagerTreeLabelProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/view/tree/StorageManagerTreeLabelProvider.java new file mode 100644 index 000000000..730b973ea --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/view/tree/StorageManagerTreeLabelProvider.java @@ -0,0 +1,47 @@ +package info.novatec.inspectit.rcp.view.tree; + +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.model.Component; +import info.novatec.inspectit.rcp.provider.ILocalStorageDataProvider; +import info.novatec.inspectit.rcp.provider.IStorageDataProvider; + +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.swt.graphics.Image; + +/** + * Styled cell label provider for the tree of storages. + * + * @author Ivan Senic + * + */ +public class StorageManagerTreeLabelProvider extends StyledCellIndexLabelProvider { + + /** + * {@inheritDoc} + */ + @Override + protected StyledString getStyledText(Object element, int index) { + if (element instanceof IStorageDataProvider) { + return TextFormatter.getStyledStorageDataString(((IStorageDataProvider) element).getStorageData(), ((IStorageDataProvider) element).getCmrRepositoryDefinition()); + } else if (element instanceof ILocalStorageDataProvider) { + return TextFormatter.getStyledStorageDataString(((ILocalStorageDataProvider) element).getLocalStorageData()); + } else if (element instanceof Component) { + return new StyledString(((Component) element).getName()); + } + return super.getStyledText(element, index); + } + + /** + * {@inheritDoc} + */ + @Override + protected Image getColumnImage(Object element, int index) { + if (index == 0) { + if (element instanceof Component) { + return ((Component) element).getImage(); + } + } + return super.getColumnImage(element, index); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/view/tree/TreeContentProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/view/tree/TreeContentProvider.java new file mode 100644 index 000000000..b0f16b05d --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/view/tree/TreeContentProvider.java @@ -0,0 +1,141 @@ +package info.novatec.inspectit.rcp.view.tree; + +import info.novatec.inspectit.rcp.model.Composite; +import info.novatec.inspectit.rcp.model.TreeModelManager; +import info.novatec.inspectit.rcp.util.ListenerList; + +import java.util.Iterator; + +import org.eclipse.core.runtime.jobs.IJobChangeListener; +import org.eclipse.jface.viewers.AbstractTreeViewer; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.ui.progress.DeferredTreeContentManager; + +/** + * The content provider for the tree viewer used for every single available CMR. + * + * @author Patrice Bouillet + * + */ +public class TreeContentProvider implements ITreeContentProvider { + + /** + * The manager is used to access the deferred objects. + */ + private DeferredTreeContentManager manager; + + /** + * Listeners that will be passed to the {@link DeferredTreeContentManager}. + */ + private ListenerList updateCompleteListenerList; + + /** + * {@inheritDoc} + */ + public Object[] getChildren(Object parentElement) { + if (manager.isDeferredAdapter(parentElement)) { + Object[] children = manager.getChildren(parentElement); + + return children; + } else if (parentElement instanceof Composite) { + // direct access to the children + Composite composite = (Composite) parentElement; + return composite.getChildren().toArray(); + } + return new Object[0]; + } + + /** + * {@inheritDoc} + */ + public Object getParent(Object element) { + if (element instanceof Composite) { + Composite composite = (Composite) element; + return composite.getParent(); + } + + return null; + } + + /** + * {@inheritDoc} + */ + public boolean hasChildren(Object element) { + if (null == element) { + return false; + } + + if (manager.isDeferredAdapter(element)) { + return manager.mayHaveChildren(element); + } + + if (element instanceof Composite) { + Composite composite = (Composite) element; + return composite.hasChildren(); + } + + return false; + } + + /** + * {@inheritDoc} + */ + public Object[] getElements(Object inputElement) { + TreeModelManager treeModelManager = (TreeModelManager) inputElement; + return treeModelManager.getRootElements(); + } + + /** + * {@inheritDoc} + */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + manager = new DeferredTreeContentManager((AbstractTreeViewer) viewer); + if (null != updateCompleteListenerList) { + for (Iterator it = updateCompleteListenerList.iterator(); it.hasNext();) { + manager.addUpdateCompleteListener(it.next()); + } + } + } + + /** + * Adds the listener to the update job that updates the elements. The listener will be added to + * the {@link DeferredTreeContentManager} if one is initialized. In any case the listener will + * be added when the new manager is initialized in the future. + * + * @param listener + * {@link IJobChangeListener} + */ + public void addUpdateCompleteListener(IJobChangeListener listener) { + if (null == updateCompleteListenerList) { + updateCompleteListenerList = new ListenerList(); + } + updateCompleteListenerList.add(listener); + if (null != manager) { + manager.addUpdateCompleteListener(listener); + } + } + + /** + * Adds the listener to the update job that updates the elements. The listener will be removed + * from the {@link DeferredTreeContentManager} if one is initialized. + * + * @param listener + * {@link IJobChangeListener} + */ + public void removeUpdateCompleteListener(IJobChangeListener listener) { + if (null != updateCompleteListenerList) { + updateCompleteListenerList.remove(listener); + } + if (null != manager) { + manager.removeUpdateCompleteListener(listener); + } + } + + /** + * {@inheritDoc} + */ + public void dispose() { + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/view/tree/TreeLabelProvider.java b/inspectIT/src/info/novatec/inspectit/rcp/view/tree/TreeLabelProvider.java new file mode 100644 index 000000000..690f16aef --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/view/tree/TreeLabelProvider.java @@ -0,0 +1,89 @@ +package info.novatec.inspectit.rcp.view.tree; + +import info.novatec.inspectit.rcp.model.Component; + +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Display; + +/** + * @author Patrice Bouillet + * + */ +public class TreeLabelProvider extends ColumnLabelProvider { + + /** + * {@inheritDoc} + */ + @Override + public Image getImage(Object element) { + if (element instanceof Component) { + Component component = (Component) element; + return component.getImage(); + } + + return super.getImage(element); + } + + /** + * {@inheritDoc} + */ + @Override + public String getText(Object element) { + if (element instanceof Component) { + Component component = (Component) element; + return component.getName(); + } + + return super.getText(element); + } + + /** + * {@inheritDoc} + */ + @Override + public String getToolTipText(Object element) { + if (element instanceof Component) { + Component component = (Component) element; + return component.getTooltip(); + } + + return super.getToolTipText(element); + } + + /** + * {@inheritDoc} + */ + @Override + public Point getToolTipShift(Object object) { + int x = 5; + int y = 5; + return new Point(x, y); + } + + /** + * {@inheritDoc} + */ + @Override + public int getToolTipDisplayDelayTime(Object object) { + return 500; + } + + /** + * {@inheritDoc} + */ + @Override + public Color getForeground(Object element) { + if (element instanceof Component) { + Component component = (Component) element; + if (!component.isEnabled()) { + return Display.getDefault().getSystemColor(SWT.COLOR_TITLE_INACTIVE_FOREGROUND); + } + } + return super.getForeground(element); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/view/tree/TreeViewerComparator.java b/inspectIT/src/info/novatec/inspectit/rcp/view/tree/TreeViewerComparator.java new file mode 100644 index 000000000..b76479bf5 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/view/tree/TreeViewerComparator.java @@ -0,0 +1,59 @@ +package info.novatec.inspectit.rcp.view.tree; + +import info.novatec.inspectit.rcp.model.DeferredComposite; + +import org.eclipse.jface.viewers.ContentViewer; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.jface.viewers.TreePathViewerSorter; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.ui.progress.PendingUpdateAdapter; + +/** + * This comparator is used to sort the elements in the server view. Only the ones in the + * instrumentation browser are affected by the sorting. Additionally, the + * {@link PendingUpdateAdapter} will always be displayed as the last element. + * + * @author Patrice Bouillet + * + */ +public class TreeViewerComparator extends TreePathViewerSorter { + + /** + * {@inheritDoc} + */ + @Override + public int compare(Viewer viewer, TreePath parentPath, Object e1, Object e2) { + if (null == parentPath) { + return 1; + } + + if (e1 instanceof PendingUpdateAdapter) { + return -1; + } + + if (parentPath.getLastSegment() instanceof DeferredComposite) { + IBaseLabelProvider prov = ((ContentViewer) viewer).getLabelProvider(); + ILabelProvider lprov = (ILabelProvider) prov; + String name1 = lprov.getText(e1); + String name2 = lprov.getText(e2); + + boolean e1LowerCase = Character.isLowerCase(name1.charAt(0)); + boolean e2LowerCase = Character.isLowerCase(name2.charAt(0)); + + if (e1LowerCase && e2LowerCase) { + return super.compare(viewer, parentPath, e1, e2); + } else if (!e1LowerCase && !e2LowerCase) { + return super.compare(viewer, parentPath, e1, e2); + } else if (e1LowerCase) { + return -1; + } else { + return 1; + } + } + + return 1; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/view/util/SelectionProviderIntermediate.java b/inspectIT/src/info/novatec/inspectit/rcp/view/util/SelectionProviderIntermediate.java new file mode 100644 index 000000000..4df304bce --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/view/util/SelectionProviderIntermediate.java @@ -0,0 +1,127 @@ +package info.novatec.inspectit.rcp.view.util; + +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.jface.viewers.IPostSelectionProvider; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; + +// NOCHKALL: Not our code. + +/** + * IPostSelectionProvider implementation that delegates to another ISelectionProvider or + * IPostSelectionProvider. The selection provider used for delegation can be exchanged dynamically. + * Registered listeners are adjusted accordingly. This utility class may be used in workbench parts + * with multiple viewers. + *

+ * IMPORTANT: The class code is copied/taken/based from Eclipse Workbench article. Original author is Marc R. Hoffmann. License info can be found here. Copied from + * + * + * @author Marc R. Hoffmann + */ +public class SelectionProviderIntermediate implements IPostSelectionProvider { + + private final ListenerList selectionListeners = new ListenerList(); + + private final ListenerList postSelectionListeners = new ListenerList(); + + private ISelectionProvider delegate; + + private ISelectionChangedListener selectionListener = new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + if (event.getSelectionProvider() == delegate) { + fireSelectionChanged(event.getSelection()); + } + } + }; + + private ISelectionChangedListener postSelectionListener = new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + if (event.getSelectionProvider() == delegate) { + firePostSelectionChanged(event.getSelection()); + } + } + }; + + /** + * Sets a new selection provider to delegate to. Selection listeners registered with the + * previous delegate are removed before. + * + * @param newDelegate + * new selection provider + */ + public void setSelectionProviderDelegate(ISelectionProvider newDelegate) { + if (delegate == newDelegate) { // NOPMD + return; + } + if (delegate != null) { + delegate.removeSelectionChangedListener(selectionListener); + if (delegate instanceof IPostSelectionProvider) { + ((IPostSelectionProvider) delegate).removePostSelectionChangedListener(postSelectionListener); + } + } + delegate = newDelegate; + if (newDelegate != null) { + newDelegate.addSelectionChangedListener(selectionListener); + if (newDelegate instanceof IPostSelectionProvider) { + ((IPostSelectionProvider) newDelegate).addPostSelectionChangedListener(postSelectionListener); + } + fireSelectionChanged(newDelegate.getSelection()); + firePostSelectionChanged(newDelegate.getSelection()); + } + } + + protected void fireSelectionChanged(ISelection selection) { + fireSelectionChanged(selectionListeners, selection); + } + + protected void firePostSelectionChanged(ISelection selection) { + fireSelectionChanged(postSelectionListeners, selection); + } + + private void fireSelectionChanged(ListenerList list, ISelection selection) { + SelectionChangedEvent event = new SelectionChangedEvent(delegate, selection); + Object[] listeners = list.getListeners(); + for (int i = 0; i < listeners.length; i++) { + ISelectionChangedListener listener = (ISelectionChangedListener) listeners[i]; + listener.selectionChanged(event); + } + } + + // IPostSelectionProvider Implementation + + public void addSelectionChangedListener(ISelectionChangedListener listener) { + selectionListeners.add(listener); + } + + public void removeSelectionChangedListener(ISelectionChangedListener listener) { + selectionListeners.remove(listener); + } + + public void addPostSelectionChangedListener(ISelectionChangedListener listener) { + postSelectionListeners.add(listener); + } + + public void removePostSelectionChangedListener(ISelectionChangedListener listener) { + postSelectionListeners.remove(listener); + } + + public ISelection getSelection() { + return delegate == null ? null : delegate.getSelection(); + } + + public void setSelection(ISelection selection) { + if (delegate != null) { + delegate.setSelection(selection); + } + } + + public ISelectionProvider getSelectionProviderDelegate() { + return delegate; + } + +} \ No newline at end of file diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/AddCmrRepositoryWizard.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/AddCmrRepositoryWizard.java new file mode 100644 index 000000000..1b56ff3a3 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/AddCmrRepositoryWizard.java @@ -0,0 +1,83 @@ +package info.novatec.inspectit.rcp.wizard; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.wizard.page.DefineCmrWizardPage; +import info.novatec.inspectit.rcp.wizard.page.PreviewCmrDataWizardPage; + +import java.util.Objects; + +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IWorkbench; + +/** + * Wizard for adding the {@link info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition}. + * + * @author Ivan Senic + * + */ +public class AddCmrRepositoryWizard extends Wizard implements INewWizard { + + /** + * {@link DefineCmrWizardPage}. + */ + private DefineCmrWizardPage defineCmrWizardPage; + + /** + * {@link PreviewCmrDataWizardPage}. + */ + private PreviewCmrDataWizardPage previewCmrDataWizardPage; + + /** + * Default constructor. + */ + public AddCmrRepositoryWizard() { + this.setWindowTitle("Add Central Management Repository (CMR)"); + this.setDefaultPageImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_WIZBAN_SERVER)); + } + + /** + * {@inheritDoc} + */ + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + } + + /** + * {@inheritDoc} + */ + @Override + public void addPages() { + defineCmrWizardPage = new DefineCmrWizardPage("Add New CMR Repository"); + addPage(defineCmrWizardPage); + previewCmrDataWizardPage = new PreviewCmrDataWizardPage(); + addPage(previewCmrDataWizardPage); + } + + /** + * {@inheritDoc} + */ + @Override + public IWizardPage getNextPage(IWizardPage page) { + if (Objects.equals(page, defineCmrWizardPage)) { + previewCmrDataWizardPage.cancel(); + previewCmrDataWizardPage.update(defineCmrWizardPage.getCmrRepositoryDefinition()); + } + return super.getNextPage(page); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean performFinish() { + CmrRepositoryDefinition cmrRepositoryDefinition = defineCmrWizardPage.getCmrRepositoryDefinition(); + InspectIT.getDefault().getCmrRepositoryManager().addCmrRepositoryDefinition(cmrRepositoryDefinition); + return true; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/AddStorageLabelWizard.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/AddStorageLabelWizard.java new file mode 100644 index 000000000..a9c5b75e9 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/AddStorageLabelWizard.java @@ -0,0 +1,99 @@ +package info.novatec.inspectit.rcp.wizard; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.provider.IStorageDataProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.wizard.page.AddStorageLabelWizardPage; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; + +import java.util.List; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IWorkbench; + +/** + * Wizard for adding label to storage. + * + * @author Ivan Senic + * + */ +public class AddStorageLabelWizard extends Wizard implements INewWizard { + + /** + * {@link StorageData} to add label. + */ + private StorageData storageData; + + /** + * {@link CmrRepositoryDefinition} where storage is located. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * Page for wizard. + */ + private AddStorageLabelWizardPage addStorageLabelWizardPage; + + /** + * Default constructor. + * + * @param storageDataProvider + * Selected provider. + */ + public AddStorageLabelWizard(IStorageDataProvider storageDataProvider) { + super(); + Assert.isNotNull(storageDataProvider); + this.setWindowTitle("Add Storage Label Wizard"); + this.setDefaultPageImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_WIZBAN_LABEL)); + this.storageData = storageDataProvider.getStorageData(); + this.cmrRepositoryDefinition = storageDataProvider.getCmrRepositoryDefinition(); + } + + /** + * {@inheritDoc} + */ + @Override + public void addPages() { + addStorageLabelWizardPage = new AddStorageLabelWizardPage(storageData, cmrRepositoryDefinition); + addPage(addStorageLabelWizardPage); + } + + /** + * {@inheritDoc} + */ + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + } + + /** + * {@inheritDoc} + */ + @Override + public boolean performFinish() { + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + List> labelsToAdd = addStorageLabelWizardPage.getLabelsToAdd(); + try { + StorageData updatedStorageData = cmrRepositoryDefinition.getStorageService().addLabelsToStorage(storageData, labelsToAdd, true); + try { + InspectIT.getDefault().getInspectITStorageManager().storageRemotelyUpdated(updatedStorageData); + } catch (Exception e) { + InspectIT.getDefault().createErrorDialog("Error occurred trying to save local storage data to disk.", e, -1); + } + } catch (StorageException e) { + InspectIT.getDefault().createErrorDialog("Adding label to storage failed.", e, -1); + return false; + } + } else { + InspectIT.getDefault().createErrorDialog("Adding label to storage failed. Selected CMR repository is currently not available.", -1); + return false; + } + return true; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/CopyBufferToStorageWizard.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/CopyBufferToStorageWizard.java new file mode 100644 index 000000000..e39dd860a --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/CopyBufferToStorageWizard.java @@ -0,0 +1,283 @@ +package info.novatec.inspectit.rcp.wizard; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.view.impl.StorageManagerView; +import info.novatec.inspectit.rcp.wizard.page.AddStorageLabelWizardPage; +import info.novatec.inspectit.rcp.wizard.page.DefineDataProcessorsWizardPage; +import info.novatec.inspectit.rcp.wizard.page.DefineNewStorageWizzardPage; +import info.novatec.inspectit.rcp.wizard.page.DefineTimelineWizardPage; +import info.novatec.inspectit.rcp.wizard.page.NewOrExistsingStorageWizardPage; +import info.novatec.inspectit.rcp.wizard.page.SelectAgentsWizardPage; +import info.novatec.inspectit.rcp.wizard.page.SelectExistingStorageWizardPage; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.processor.AbstractDataProcessor; +import info.novatec.inspectit.storage.processor.impl.TimeFrameDataProcessor; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.progress.IProgressConstants; + +/** + * Wizard for copying the buffer content of the {@link CmrRepositoryDefinition} to Storage. + * + * @author Ivan Senic + * + */ +public class CopyBufferToStorageWizard extends Wizard implements INewWizard { + + /** + * {@link CmrRepositoryDefinition} to perform operation on. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * The collection of agents that will be automatically selected in the + * {@link SelectExistingStorageWizardPage}. + */ + private Collection autoSelectedAgents; + + /** + * Should new storage be used, or an existing one. + */ + private NewOrExistsingStorageWizardPage newOrExistsingStorageWizardPage; + + /** + * New storage wizard page. + */ + private DefineNewStorageWizzardPage defineNewStorageWizzardPage; + + /** + * Select existing storage wizard page. + */ + private SelectExistingStorageWizardPage selectExistingStorageWizardPage; + + /** + * Page to selection options. + */ + private SelectAgentsWizardPage selectAgentsPage; + + /** + * Page for defining the processors. + */ + private DefineDataProcessorsWizardPage defineProcessorsPage; + + /** + * Page for selecting the time frame. + */ + private DefineTimelineWizardPage timelineWizardPage; + + /** + * Add new label wizard page. + */ + private AddStorageLabelWizardPage addLabelWizardPage; + + /** + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to perform operation on. + */ + public CopyBufferToStorageWizard(CmrRepositoryDefinition cmrRepositoryDefinition) { + this(cmrRepositoryDefinition, Collections. emptyList()); + } + + /** + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to perform operation on. + * @param autoSelectedAgents + * The collection of agents that will be automatically selected in the + * {@link SelectExistingStorageWizardPage}. + */ + public CopyBufferToStorageWizard(CmrRepositoryDefinition cmrRepositoryDefinition, Collection autoSelectedAgents) { + super(); + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + this.autoSelectedAgents = autoSelectedAgents; + this.setWindowTitle("Copy Buffer to Storage Wizard"); + this.setDefaultPageImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_WIZBAN_STORAGE)); + } + + /** + * {@inheritDoc} + */ + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + } + + /** + * {@inheritDoc} + */ + @Override + public void addPages() { + newOrExistsingStorageWizardPage = new NewOrExistsingStorageWizardPage(); + addPage(newOrExistsingStorageWizardPage); + defineNewStorageWizzardPage = new DefineNewStorageWizzardPage(cmrRepositoryDefinition); + addPage(defineNewStorageWizzardPage); + selectExistingStorageWizardPage = new SelectExistingStorageWizardPage(cmrRepositoryDefinition, false); + addPage(selectExistingStorageWizardPage); + selectAgentsPage = new SelectAgentsWizardPage("Select Agent(s) to be copied", autoSelectedAgents); + addPage(selectAgentsPage); + defineProcessorsPage = new DefineDataProcessorsWizardPage(DefineDataProcessorsWizardPage.BUFFER_DATA | DefineDataProcessorsWizardPage.SYSTEM_DATA); + addPage(defineProcessorsPage); + timelineWizardPage = new DefineTimelineWizardPage("Limit Data", "Optionally select set of data to be copied by defining time frame", DefineTimelineWizardPage.PAST + | DefineTimelineWizardPage.BOTH_DATES); + addPage(timelineWizardPage); + addLabelWizardPage = new AddStorageLabelWizardPage(cmrRepositoryDefinition); + addPage(addLabelWizardPage); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean performFinish() { + final StorageData storageData; + final CmrRepositoryDefinition cmrRepositoryDefinition; + final boolean autoFinalize; + + if (newOrExistsingStorageWizardPage.useNewStorage()) { + storageData = defineNewStorageWizzardPage.getStorageData(); + cmrRepositoryDefinition = defineNewStorageWizzardPage.getSelectedRepository(); + autoFinalize = defineNewStorageWizzardPage.isAutoFinalize(); + } else { + storageData = selectExistingStorageWizardPage.getSelectedStorageData(); + cmrRepositoryDefinition = selectExistingStorageWizardPage.getSelectedRepository(); + autoFinalize = selectExistingStorageWizardPage.isAutoFinalize(); + } + + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + final List agents = selectAgentsPage.getSelectedAgents(); + List processors = defineProcessorsPage.getProcessorList(); + if (timelineWizardPage.isTimerframeUsed()) { + TimeFrameDataProcessor timeFrameDataProcessor = timelineWizardPage.getTimeFrameDataProcessor(processors); + processors = new ArrayList(1); + processors.add(timeFrameDataProcessor); + } + + final List finalProcessors = processors; + Job copyBufferJob = new Job("Copy Buffer to Storage") { + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + monitor.beginTask("Copying the content of repository buffer to storage.", IProgressMonitor.UNKNOWN); + StorageData copiedStorage = cmrRepositoryDefinition.getStorageService().copyBufferToStorage(storageData, agents, finalProcessors, autoFinalize); + List> labels = addLabelWizardPage.getLabelsToAdd(); + if (!labels.isEmpty()) { + cmrRepositoryDefinition.getStorageService().addLabelsToStorage(copiedStorage, labels, true); + } + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + IViewPart storageManagerView = activePage.findView(StorageManagerView.VIEW_ID); + if (storageManagerView instanceof StorageManagerView) { + ((StorageManagerView) storageManagerView).refresh(cmrRepositoryDefinition); + } + } + }); + } catch (final StorageException e) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + InspectIT.getDefault().createErrorDialog("Copy of the buffer data to storage failed.", e, -1); + } + }); + return Status.CANCEL_STATUS; + } + return Status.OK_STATUS; + } + }; + copyBufferJob.setUser(true); + copyBufferJob.setProperty(IProgressConstants.ICON_PROPERTY, InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_BUFFER_COPY)); + copyBufferJob.schedule(); + } else { + InspectIT.getDefault().createErrorDialog("Copy of the buffer data to storage failed. Selected CMR repository is currently not available.", -1); + return false; + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public IWizardPage getNextPage(IWizardPage page) { + if (ObjectUtils.equals(page, newOrExistsingStorageWizardPage)) { + if (newOrExistsingStorageWizardPage.useNewStorage()) { + return defineNewStorageWizzardPage; + } else { + return selectExistingStorageWizardPage; + } + } else if (ObjectUtils.equals(page, defineNewStorageWizzardPage)) { + selectAgentsPage.setCmrRepositoryDefinition(cmrRepositoryDefinition); + addLabelWizardPage.setStorageData(defineNewStorageWizzardPage.getStorageData()); + return selectAgentsPage; + } else if (ObjectUtils.equals(page, selectExistingStorageWizardPage)) { + selectAgentsPage.setCmrRepositoryDefinition(cmrRepositoryDefinition); + addLabelWizardPage.setStorageData(selectExistingStorageWizardPage.getSelectedStorageData()); + return selectAgentsPage; + } else { + return super.getNextPage(page); + } + } + + /** + * {@inheritDoc} + */ + @Override + public IWizardPage getPreviousPage(IWizardPage page) { + if (ObjectUtils.equals(page, defineNewStorageWizzardPage) || ObjectUtils.equals(page, selectExistingStorageWizardPage)) { + return newOrExistsingStorageWizardPage; + } else if (ObjectUtils.equals(page, selectAgentsPage)) { + if (newOrExistsingStorageWizardPage.useNewStorage()) { + return defineNewStorageWizzardPage; + } else { + return selectExistingStorageWizardPage; + } + } else { + return super.getPreviousPage(page); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canFinish() { + if (!newOrExistsingStorageWizardPage.isPageComplete()) { + return false; + } else if (newOrExistsingStorageWizardPage.useNewStorage() && !defineNewStorageWizzardPage.isPageComplete()) { + return false; + } else if (!newOrExistsingStorageWizardPage.useNewStorage() && !selectExistingStorageWizardPage.isPageComplete()) { + return false; + } else if (!selectAgentsPage.isPageComplete()) { + return false; + } else if (!defineProcessorsPage.isPageComplete()) { + return false; + } else if (!timelineWizardPage.isPageComplete()) { + return false; + } + return true; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/CopyDataToStorageWizard.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/CopyDataToStorageWizard.java new file mode 100644 index 000000000..5a00a4cd1 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/CopyDataToStorageWizard.java @@ -0,0 +1,280 @@ +package info.novatec.inspectit.rcp.wizard; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.IIdsAwareAggregatedData; +import info.novatec.inspectit.communication.data.AggregatedExceptionSensorData; +import info.novatec.inspectit.communication.data.AggregatedHttpTimerData; +import info.novatec.inspectit.communication.data.AggregatedSqlStatementData; +import info.novatec.inspectit.communication.data.AggregatedTimerData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.InvocationAwareData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.view.impl.StorageManagerView; +import info.novatec.inspectit.rcp.wizard.page.AddStorageLabelWizardPage; +import info.novatec.inspectit.rcp.wizard.page.DefineDataProcessorsWizardPage; +import info.novatec.inspectit.rcp.wizard.page.DefineNewStorageWizzardPage; +import info.novatec.inspectit.rcp.wizard.page.NewOrExistsingStorageWizardPage; +import info.novatec.inspectit.rcp.wizard.page.SelectExistingStorageWizardPage; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.processor.AbstractDataProcessor; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PlatformUI; + +/** + * Wizard for copying the selected data to one storage. + * + * @author Ivan Senic + * + */ +public class CopyDataToStorageWizard extends Wizard implements INewWizard { + + /** + * Collection of data to be copied. + */ + private Collection copyDataList; + + /** + * CMR for the action. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * Page for selecting if new or existing storage page should be used. + */ + private NewOrExistsingStorageWizardPage newOrExistsingStorageWizardPage; + + /** + * Page for new storage. + */ + private DefineNewStorageWizzardPage defineNewStorageWizzardPage; + + /** + * Page for selecting the existing storage. + */ + private SelectExistingStorageWizardPage selectExistingStorageWizardPage; + + /** + * Selection of data to be saved. + */ + private DefineDataProcessorsWizardPage defineDataProcessorsWizardPage; + + /** + * Add label wizard page. + */ + private AddStorageLabelWizardPage addLabelWizardPage; + + /** + * Default constructor. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to execute action on. + * @param copyDataList + * Collection of data to be copied. + */ + public CopyDataToStorageWizard(CmrRepositoryDefinition cmrRepositoryDefinition, Collection copyDataList) { + this.copyDataList = copyDataList; + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + this.setWindowTitle("Save Data to Storage Wizard"); + this.setDefaultPageImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_WIZBAN_STORAGE)); + } + + /** + * {@inheritDoc} + */ + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + } + + /** + * {@inheritDoc} + */ + @Override + public void addPages() { + int style = 0; + for (DefaultData copyData : copyDataList) { + Class clazz = copyData.getClass(); + if (HttpTimerData.class.equals(clazz) || AggregatedHttpTimerData.class.equals(clazz)) { + style |= DefineDataProcessorsWizardPage.ONLY_HTTP_TIMERS; + } else if (SqlStatementData.class.equals(clazz) || AggregatedSqlStatementData.class.equals(clazz)) { + style |= DefineDataProcessorsWizardPage.ONLY_SQL_STATEMENTS; + } else if (ExceptionSensorData.class.equals(clazz) || AggregatedExceptionSensorData.class.equals(clazz)) { + style |= DefineDataProcessorsWizardPage.ONLY_EXCEPTIONS; + } else if (TimerData.class.equals(clazz) || AggregatedTimerData.class.equals(clazz)) { + style |= DefineDataProcessorsWizardPage.ONLY_TIMERS; + } else if (InvocationSequenceData.class.equals(clazz)) { + style |= DefineDataProcessorsWizardPage.ONLY_INVOCATIONS | DefineDataProcessorsWizardPage.EXTRACT_INVOCATIONS; + } + } + newOrExistsingStorageWizardPage = new NewOrExistsingStorageWizardPage(); + addPage(newOrExistsingStorageWizardPage); + defineNewStorageWizzardPage = new DefineNewStorageWizzardPage(cmrRepositoryDefinition); + addPage(defineNewStorageWizzardPage); + selectExistingStorageWizardPage = new SelectExistingStorageWizardPage(cmrRepositoryDefinition, false); + addPage(selectExistingStorageWizardPage); + defineDataProcessorsWizardPage = new DefineDataProcessorsWizardPage(style); + addPage(defineDataProcessorsWizardPage); + addLabelWizardPage = new AddStorageLabelWizardPage(cmrRepositoryDefinition); + addPage(addLabelWizardPage); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean performFinish() { + final StorageData storageData; + final boolean autoFinalize; + if (newOrExistsingStorageWizardPage.useNewStorage()) { + storageData = defineNewStorageWizzardPage.getStorageData(); + autoFinalize = defineNewStorageWizzardPage.isAutoFinalize(); + } else { + storageData = selectExistingStorageWizardPage.getSelectedStorageData(); + autoFinalize = selectExistingStorageWizardPage.isAutoFinalize(); + } + + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + // prepare for save + final Collection processors = defineDataProcessorsWizardPage.getProcessorList(); + final Set idSet = new HashSet(); + Set platformIdents = new HashSet(); + for (DefaultData template : copyDataList) { + if (template instanceof IIdsAwareAggregatedData) { + // if we have aggregated data add all objects that were included in the + // aggregation + idSet.addAll(((IIdsAwareAggregatedData) template).getAggregatedIds()); + } else if (0 != template.getId()) { + idSet.add(template.getId()); + } + if (template instanceof InvocationAwareData) { + // if we have invocation aware object, add also all invocations + // data processor will filter the correct data to save + idSet.addAll(((InvocationAwareData) template).getInvocationParentsIdSet()); + } + platformIdents.add(template.getPlatformIdent()); + } + final long platformIdent = (platformIdents.size() == 1) ? platformIdents.iterator().next() : 0; + + // create and execute job + Job copyDataJob = new Job("Copy Data to Buffer") { + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + StorageData updatedStorageData = cmrRepositoryDefinition.getStorageService().copyDataToStorage(storageData, idSet, platformIdent, processors, autoFinalize); + List> labels = addLabelWizardPage.getLabelsToAdd(); + if (!labels.isEmpty()) { + cmrRepositoryDefinition.getStorageService().addLabelsToStorage(updatedStorageData, labels, true); + } + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + IViewPart storageManagerView = activePage.findView(StorageManagerView.VIEW_ID); + if (storageManagerView instanceof StorageManagerView) { + ((StorageManagerView) storageManagerView).refresh(cmrRepositoryDefinition); + } + } + }); + } catch (final StorageException e) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + InspectIT.getDefault().createErrorDialog("Copy data to buffer failed.", e, -1); + } + }); + } + return Status.OK_STATUS; + } + }; + copyDataJob.setUser(true); + copyDataJob.schedule(); + return true; + } + + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public IWizardPage getNextPage(IWizardPage page) { + if (ObjectUtils.equals(page, newOrExistsingStorageWizardPage)) { + if (newOrExistsingStorageWizardPage.useNewStorage()) { + return defineNewStorageWizzardPage; + } else { + return selectExistingStorageWizardPage; + } + } else if (ObjectUtils.equals(page, defineNewStorageWizzardPage)) { + addLabelWizardPage.setStorageData(defineNewStorageWizzardPage.getStorageData()); + return defineDataProcessorsWizardPage; + } else if (ObjectUtils.equals(page, selectExistingStorageWizardPage)) { + addLabelWizardPage.setStorageData(selectExistingStorageWizardPage.getSelectedStorageData()); + return defineDataProcessorsWizardPage; + } else { + return super.getNextPage(page); + } + } + + /** + * {@inheritDoc} + */ + @Override + public IWizardPage getPreviousPage(IWizardPage page) { + if (ObjectUtils.equals(page, defineNewStorageWizzardPage) || ObjectUtils.equals(page, selectExistingStorageWizardPage)) { + return newOrExistsingStorageWizardPage; + } else if (ObjectUtils.equals(page, defineDataProcessorsWizardPage)) { + if (newOrExistsingStorageWizardPage.useNewStorage()) { + return defineNewStorageWizzardPage; + } else { + return selectExistingStorageWizardPage; + } + } else { + return super.getPreviousPage(page); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canFinish() { + if (!newOrExistsingStorageWizardPage.isPageComplete()) { + return false; + } else if (newOrExistsingStorageWizardPage.useNewStorage() && !defineNewStorageWizzardPage.isPageComplete()) { + return false; + } else if (!newOrExistsingStorageWizardPage.useNewStorage() && !selectExistingStorageWizardPage.isPageComplete()) { + return false; + } else if (!defineDataProcessorsWizardPage.isPageComplete()) { + return false; + } + return true; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/CreateStorageWizard.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/CreateStorageWizard.java new file mode 100644 index 000000000..fd1c1bde0 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/CreateStorageWizard.java @@ -0,0 +1,100 @@ +package info.novatec.inspectit.rcp.wizard; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.view.impl.StorageManagerView; +import info.novatec.inspectit.rcp.wizard.page.DefineNewStorageWizzardPage; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageException; + +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.PlatformUI; + +/** + * Wizard for creating and opening the storage. + * + * @author Ivan Senic + * + */ +public class CreateStorageWizard extends Wizard implements INewWizard { + + /** + * New storage page. + */ + private DefineNewStorageWizzardPage defineNewStoragePage; + + /** + * Selected CMR repository. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * Default constructor. + */ + public CreateStorageWizard() { + super(); + this.setWindowTitle("Create Storage Wizard"); + this.setDefaultPageImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_WIZBAN_STORAGE)); + } + + /** + * This constructor will set provided {@link CmrRepositoryDefinition} as the initially selected + * repository to create storage to. Force open, means that the option if the storage will be + * opened or not, will not be available for the user. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to create storage on. + */ + public CreateStorageWizard(CmrRepositoryDefinition cmrRepositoryDefinition) { + this(); + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + } + + /** + * {@inheritDoc} + */ + @Override + public void addPages() { + defineNewStoragePage = new DefineNewStorageWizzardPage(cmrRepositoryDefinition, false); + addPage(defineNewStoragePage); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean performFinish() { + CmrRepositoryDefinition cmrRepositoryDefinition = defineNewStoragePage.getSelectedRepository(); + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + StorageData storageData = defineNewStoragePage.getStorageData(); + try { + cmrRepositoryDefinition.getStorageService().createAndOpenStorage(storageData); + IViewPart viewPart = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().findView(StorageManagerView.VIEW_ID); + if (viewPart instanceof StorageManagerView) { + ((StorageManagerView) viewPart).refresh(); + } + } catch (StorageException e) { + InspectIT.getDefault().createErrorDialog("Storage can not be created.", e, -1); + return false; + } + } else { + InspectIT.getDefault().createErrorDialog("Storage can not be created. Selected CMR repository is currently not available.", -1); + return false; + } + return true; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/DownloadStorageWizard.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/DownloadStorageWizard.java new file mode 100644 index 000000000..9d1053a94 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/DownloadStorageWizard.java @@ -0,0 +1,206 @@ +package info.novatec.inspectit.rcp.wizard; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.provider.IStorageDataProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.view.impl.StorageManagerView; +import info.novatec.inspectit.rcp.wizard.page.StorageCompressionWizardPage; +import info.novatec.inspectit.storage.StorageData; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.progress.IProgressConstants; + +/** + * Wizard for downloading storage. + * + * @author Ivan Senic + * + */ +public class DownloadStorageWizard extends Wizard implements INewWizard { + + /** + * List of storages to be downloaded provided by {@link IStorageDataProvider}s. + */ + private Collection storageDataProviders; + + /** + * Wizard page. + */ + private StorageCompressionWizardPage storageCompressionWizardPage; + + /** + * Default constructor. + * + * @param storageDataProviders + * List of storages to be downloaded provided by {@link IStorageDataProvider}s. + */ + public DownloadStorageWizard(Collection storageDataProviders) { + Assert.isTrue(CollectionUtils.isNotEmpty(storageDataProviders)); + this.storageDataProviders = storageDataProviders; + if (storageDataProviders.size() == 1) { + this.setWindowTitle("Download Storage"); + } else { + this.setWindowTitle("Download " + storageDataProviders.size() + " Storages"); + } + this.setDefaultPageImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_WIZBAN_DOWNLOAD)); + } + + /** + * {@inheritDoc} + */ + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + } + + /** + * {@inheritDoc} + */ + @Override + public void addPages() { + String title = getWindowTitle(); + long totalSize = 0; + for (IStorageDataProvider storageDataProvider : storageDataProviders) { + totalSize += storageDataProvider.getStorageData().getDiskSize(); + } + StringBuilder message = new StringBuilder("Options for downloading "); + if (storageDataProviders.size() == 1) { + StorageData storageData = storageDataProviders.iterator().next().getStorageData(); + message.append("the storage '"); + message.append(storageData.getName()); + message.append("' (size: "); + message.append(NumberFormatter.formatBytesToMBytes(storageData.getDiskSize())); + message.append(')'); + } else { + message.append(storageDataProviders.size()); + message.append(" storages (total size: "); + message.append(NumberFormatter.formatBytesToMBytes(totalSize)); + message.append(')'); + } + storageCompressionWizardPage = new StorageCompressionWizardPage(title, message.toString()); + addPage(storageCompressionWizardPage); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean performFinish() { + final boolean compress = storageCompressionWizardPage.isCompressBefore(); + new DownloadStorageJob(storageDataProviders, compress).schedule(); + return true; + } + + /** + * A job for downloading a one or more storages. If an exception is caught in the Job, the Job + * will exit with Warnings status and provide a Throwable as the reason for not succeeding. + * Remaining storages will still be downloaded. + * + * @author Ivan Senic + * + */ + private static class DownloadStorageJob extends Job { + + /** + * Should download be compressed. + */ + private boolean compress; + + /** + * Collection of storages to download. + */ + private Collection storageDataProviders; + + /** + * Default constructor. + * + * @param storageDataProviders + * Collection of storages to download. + * @param compress + * Should download be compressed. + */ + public DownloadStorageJob(Collection storageDataProviders, boolean compress) { + super("Download Storages"); + this.compress = compress; + this.storageDataProviders = storageDataProviders; + setUser(true); + setProperty(IProgressConstants.ICON_PROPERTY, InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_STORAGE_DOWNLOADED)); + } + + /** + * {@inheritDoc} + */ + @Override + protected IStatus run(IProgressMonitor monitor) { + SubMonitor subMonitor = SubMonitor.convert(monitor); + List connectedStatuses = new ArrayList<>(); + + // calculate how much work we have based on storage sizes + int totalSize = 0; + for (IStorageDataProvider storageDataProvider : storageDataProviders) { + totalSize += (int) (storageDataProvider.getStorageData().getDiskSize() / 1000); + } + subMonitor.setWorkRemaining(totalSize); + + for (IStorageDataProvider storageDataProvider : storageDataProviders) { + StorageData storageData = storageDataProvider.getStorageData(); + CmrRepositoryDefinition cmrRepositoryDefinition = storageDataProvider.getCmrRepositoryDefinition(); + + if (cmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.OFFLINE) { + connectedStatuses.add(new Status(IStatus.WARNING, InspectIT.ID, "Storage '" + storageData.getName() + "'can not be downloaded because the CMR it is located on is offline.")); + continue; + } + + try { + InspectIT.getDefault().getInspectITStorageManager() + .fullyDownloadStorage(storageData, cmrRepositoryDefinition, compress, subMonitor.newChild((int) (storageData.getDiskSize() / 1000))); + } catch (Exception e) { + connectedStatuses.add(new Status(IStatus.WARNING, InspectIT.ID, "Storage '" + storageData.getName() + "'was not downloaded due to the exception", e)); + continue; + } + } + + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + IViewPart storageManagerView = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().findView(StorageManagerView.VIEW_ID); + if (storageManagerView instanceof StorageManagerView) { + ((StorageManagerView) storageManagerView).refreshWithoutCmrCall(); + } + } + }); + monitor.done(); + if (CollectionUtils.isNotEmpty(connectedStatuses)) { + if (1 == connectedStatuses.size()) { + return connectedStatuses.iterator().next(); + } else { + return new MultiStatus(InspectIT.ID, -1, connectedStatuses.toArray(new Status[connectedStatuses.size()]), "Download of several storages failed.", null); + } + } else { + return Status.OK_STATUS; + } + } + + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/EditCmrRepositoryWizard.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/EditCmrRepositoryWizard.java new file mode 100644 index 000000000..227869f28 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/EditCmrRepositoryWizard.java @@ -0,0 +1,104 @@ +package info.novatec.inspectit.rcp.wizard; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.wizard.page.DefineCmrWizardPage; +import info.novatec.inspectit.rcp.wizard.page.PreviewCmrDataWizardPage; + +import java.util.Objects; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IWorkbench; + +/** + * Wizard for editing the {@link info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition}. + * + * @author Ivan Senic + * + */ +public class EditCmrRepositoryWizard extends Wizard implements INewWizard { + + /** + * {@link DefineCmrWizardPage}. + */ + private DefineCmrWizardPage defineCmrWizardPage; + + /** + * {@link PreviewCmrDataWizardPage}. + */ + private PreviewCmrDataWizardPage previewCmrDataWizardPage; + + /** + * Repository to edit. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * Default constructor. + * + * @param cmrRepositoryDefinition + * Repository to edit + */ + public EditCmrRepositoryWizard(CmrRepositoryDefinition cmrRepositoryDefinition) { + Assert.isNotNull(cmrRepositoryDefinition); + this.setWindowTitle("Edit Central Management Repository (CMR)"); + this.setDefaultPageImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_WIZBAN_EDIT)); + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + } + + /** + * {@inheritDoc} + */ + @Override + public void addPages() { + defineCmrWizardPage = new DefineCmrWizardPage("Edit CMR Repository", cmrRepositoryDefinition); + addPage(defineCmrWizardPage); + previewCmrDataWizardPage = new PreviewCmrDataWizardPage(); + addPage(previewCmrDataWizardPage); + } + + /** + * {@inheritDoc} + */ + @Override + public IWizardPage getNextPage(IWizardPage page) { + if (Objects.equals(page, defineCmrWizardPage)) { + previewCmrDataWizardPage.cancel(); + previewCmrDataWizardPage.update(defineCmrWizardPage.getCmrRepositoryDefinition()); + } + return super.getNextPage(page); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean performFinish() { + CmrRepositoryDefinition editedRepository = defineCmrWizardPage.getCmrRepositoryDefinition(); + if (Objects.equals(editedRepository, cmrRepositoryDefinition)) { + // if they equal port and IP did not change just update the name/desc + cmrRepositoryDefinition.setName(editedRepository.getName()); + cmrRepositoryDefinition.setDescription(editedRepository.getDescription()); + InspectIT.getDefault().getCmrRepositoryManager().updateCmrRepositoryDefinitionData(cmrRepositoryDefinition); + } else { + // if not then remove and add + InspectIT.getDefault().getCmrRepositoryManager().removeCmrRepositoryDefinition(cmrRepositoryDefinition); + InspectIT.getDefault().getCmrRepositoryManager().addCmrRepositoryDefinition(editedRepository); + } + + return true; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/ExportStorageWizard.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/ExportStorageWizard.java new file mode 100644 index 000000000..d49cf95d3 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/ExportStorageWizard.java @@ -0,0 +1,207 @@ +package info.novatec.inspectit.rcp.wizard; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.storage.InspectITStorageManager; +import info.novatec.inspectit.rcp.view.impl.StorageManagerView; +import info.novatec.inspectit.rcp.wizard.page.ExportStorageWizardPage; +import info.novatec.inspectit.rcp.wizard.page.StorageCompressionWizardPage; +import info.novatec.inspectit.storage.IStorageData; +import info.novatec.inspectit.storage.LocalStorageData; +import info.novatec.inspectit.storage.StorageData; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.progress.IProgressConstants; + +/** + * Wizard for exporting the storage. + * + * @author Ivan Senic + * + */ +public class ExportStorageWizard extends Wizard implements INewWizard { + + /** + * Storage to export. + */ + private IStorageData storageData; + + /** + * Cmr repository definition. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * {@link ExportStorageWizardPage}. + */ + private ExportStorageWizardPage exportStorageWizardPage; + + /** + * Wizard page. + */ + private StorageCompressionWizardPage storageCompressionWizardPage; + + /** + * Default constructor. + */ + protected ExportStorageWizard() { + this.setWindowTitle("Export Storage"); + this.setDefaultPageImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_WIZBAN_IMPORT)); + } + + /** + * Default constructor. + * + * @param localStorageData + * Storage to export. + */ + public ExportStorageWizard(LocalStorageData localStorageData) { + this(); + this.storageData = localStorageData; + } + + /** + * Default constructor. + * + * @param storageData + * Storage to export. + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} where storage is located. + */ + public ExportStorageWizard(StorageData storageData, CmrRepositoryDefinition cmrRepositoryDefinition) { + this(); + this.storageData = storageData; + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + } + + /** + * {@inheritDoc} + */ + @Override + public void addPages() { + exportStorageWizardPage = new ExportStorageWizardPage(storageData); + addPage(exportStorageWizardPage); + if (storageData instanceof StorageData) { + StorageData remoteStorageData = (StorageData) storageData; + if (!InspectIT.getDefault().getInspectITStorageManager().isFullyDownloaded(remoteStorageData)) { + String title = "Export Storage"; + String message = "Options for exporting the storage '" + storageData.getName() + "' (size: " + NumberFormatter.formatBytesToMBytes(storageData.getDiskSize()) + ")"; + storageCompressionWizardPage = new StorageCompressionWizardPage(title, message); + addPage(storageCompressionWizardPage); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean performFinish() { + final InspectITStorageManager storageManager = InspectIT.getDefault().getInspectITStorageManager(); + final String fileName = exportStorageWizardPage.getFileName(); + LocalStorageData localStorageData = null; + if (storageData instanceof LocalStorageData) { + localStorageData = (LocalStorageData) storageData; + } else if (storageData instanceof StorageData) { + localStorageData = storageManager.getLocalDataForStorage((StorageData) storageData); + } + + if (null != localStorageData && localStorageData.isFullyDownloaded()) { + final LocalStorageData finalLocalStorageData = localStorageData; + Job exportStorageJob = new Job("Export Storage") { + @Override + protected IStatus run(IProgressMonitor monitor) { + monitor.beginTask("Exporting data..", IProgressMonitor.UNKNOWN); + try { + storageManager.zipStorageData(finalLocalStorageData, fileName); + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + IViewPart storageManagerView = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().findView(StorageManagerView.VIEW_ID); + if (storageManagerView instanceof StorageManagerView) { + ((StorageManagerView) storageManagerView).refreshWithoutCmrCall(); + } + InspectIT.getDefault().createInfoDialog("The storage was exported successfully.", -1); + } + }); + } catch (final Exception e) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + InspectIT.getDefault().createErrorDialog("Exception occurred trying to export storage.", e, -1); + } + }); + } + monitor.done(); + return Status.OK_STATUS; + } + }; + exportStorageJob.setUser(true); + exportStorageJob.setProperty(IProgressConstants.ICON_PROPERTY, InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_EXPORT)); + exportStorageJob.schedule(); + } else { + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + final boolean compress = storageCompressionWizardPage.isCompressBefore(); + Job downloadAndExportStorageJob = new Job("Download And Export Storage") { + @Override + protected IStatus run(IProgressMonitor monitor) { + SubMonitor subMonitor = SubMonitor.convert(monitor); + try { + storageManager.zipStorageData((StorageData) storageData, cmrRepositoryDefinition, fileName, compress, subMonitor); + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + InspectIT.getDefault().createInfoDialog("The storage was downloaded and exported successfully.", -1); + + } + }); + } catch (final Exception e) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + InspectIT.getDefault().createErrorDialog("Exception occurred trying to export storage.", e, -1); + } + }); + } + monitor.done(); + return Status.OK_STATUS; + + } + }; + downloadAndExportStorageJob.setUser(true); + downloadAndExportStorageJob.setProperty(IProgressConstants.ICON_PROPERTY, InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_EXPORT)); + downloadAndExportStorageJob.schedule(); + } else { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + InspectIT.getDefault().createInfoDialog("The storage could not be downloaded, the CMR Repository is offline. Export will be aborted.", -1); + } + }); + } + } + + return true; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/ImportStorageWizard.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/ImportStorageWizard.java new file mode 100644 index 000000000..b03d43656 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/ImportStorageWizard.java @@ -0,0 +1,204 @@ +package info.novatec.inspectit.rcp.wizard; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.storage.InspectITStorageManager; +import info.novatec.inspectit.rcp.view.impl.StorageManagerView; +import info.novatec.inspectit.rcp.wizard.page.ImportStorageInfoPage; +import info.novatec.inspectit.rcp.wizard.page.ImportStorageSelectPage; +import info.novatec.inspectit.storage.IStorageData; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.util.ObjectUtils; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.progress.IProgressConstants; + +/** + * Wizard for importing the storages. + * + * @author Ivan Senic + * + */ +public class ImportStorageWizard extends Wizard implements INewWizard { + + /** + * {@link ImportStorageSelectPage}. + */ + private ImportStorageSelectPage importStorageSelectPage; + + /** + * {@link ImportStorageInfoPage}. + */ + private ImportStorageInfoPage importStorageInfoPage; + + /** + * Default constructor. + */ + public ImportStorageWizard() { + this.setWindowTitle("Import Storage"); + this.setDefaultPageImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_WIZBAN_IMPORT)); + } + + /** + * {@inheritDoc} + */ + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + } + + /** + * {@inheritDoc} + */ + @Override + public void addPages() { + importStorageSelectPage = new ImportStorageSelectPage(); + addPage(importStorageSelectPage); + importStorageInfoPage = new ImportStorageInfoPage(); + addPage(importStorageInfoPage); + + } + + /** + * {@inheritDoc} + */ + @Override + public IWizardPage getNextPage(IWizardPage page) { + if (ObjectUtils.equals(page, importStorageSelectPage)) { + importStorageInfoPage.setFileName(importStorageSelectPage.getFileName()); + importStorageInfoPage.setImportLocally(importStorageSelectPage.isImportLocally()); + importStorageInfoPage.setCmrRepositoryDefinition(importStorageSelectPage.getCmrRepositoryDefinition()); + importStorageInfoPage.update(); + } + return super.getNextPage(page); + } + + /** + * {@inheritDoc} + */ + @Override + public IWizardPage getPreviousPage(IWizardPage page) { + if (ObjectUtils.equals(page, importStorageInfoPage)) { + importStorageInfoPage.reset(); + } + return super.getPreviousPage(page); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canFinish() { + if (getContainer().getCurrentPage().equals(importStorageSelectPage)) { + return false; + } else { + if (!importStorageSelectPage.isPageComplete()) { + return false; + } + if (!importStorageInfoPage.isPageComplete()) { + return false; + } + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean performFinish() { + final String fileName = importStorageSelectPage.getFileName(); + final CmrRepositoryDefinition cmrRepositoryDefinition = importStorageSelectPage.getCmrRepositoryDefinition(); + boolean importLocally = importStorageSelectPage.isImportLocally(); + if (importLocally) { + Job importStorageJob = new Job("Import Storage") { + @Override + protected IStatus run(IProgressMonitor monitor) { + monitor.beginTask("Importing data..", IProgressMonitor.UNKNOWN); + try { + InspectIT.getDefault().getInspectITStorageManager().unzipStorageData(fileName); + Display.getDefault().syncExec(new Runnable() { + @Override + public void run() { + IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + IViewPart storageManagerView = activePage.findView(StorageManagerView.VIEW_ID); + if (storageManagerView instanceof StorageManagerView) { + ((StorageManagerView) storageManagerView).refreshWithoutCmrCall(); + } + InspectIT.getDefault().createInfoDialog("Storage successfully imported.", -1); + } + }); + } catch (final Exception e) { + Display.getDefault().syncExec(new Runnable() { + @Override + public void run() { + InspectIT.getDefault().createErrorDialog("Exception occurred trying to import the storage via file.", e, -1); + } + }); + } + monitor.done(); + return Status.OK_STATUS; + } + }; + importStorageJob.setUser(true); + importStorageJob.setProperty(IProgressConstants.ICON_PROPERTY, InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_IMPORT)); + importStorageJob.schedule(); + } else { + Job importStorageJob = new Job("Import Storage") { + @Override + protected IStatus run(IProgressMonitor monitor) { + SubMonitor subMonitor = SubMonitor.convert(monitor); + subMonitor.setWorkRemaining(10); // 9 units for uploading, 1 for unpacking + InspectITStorageManager storageManager = InspectIT.getDefault().getInspectITStorageManager(); + try { + storageManager.uploadZippedStorage(fileName, cmrRepositoryDefinition, subMonitor.newChild(9)); + } catch (final Exception e) { + return new Status(Status.ERROR, InspectIT.ID, "Storage data was not successfully uploaded to the CMR. Import failed.", e); + } + + IProgressMonitor unpackMonitor = subMonitor.newChild(1); + unpackMonitor.setTaskName("Unpacking data.."); + try { + IStorageData storageData = storageManager.getStorageDataFromZip(fileName); + cmrRepositoryDefinition.getStorageService().unpackUploadedStorage(storageData); + } catch (final StorageException e) { + return new Status(Status.ERROR, InspectIT.ID, "Storage data was not successfully unpacked on the CMR. Import failed.", e); + } + unpackMonitor.done(); + + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + IViewPart storageManagerView = activePage.findView(StorageManagerView.VIEW_ID); + if (storageManagerView instanceof StorageManagerView) { + ((StorageManagerView) storageManagerView).refresh(cmrRepositoryDefinition); + } + InspectIT.getDefault().createInfoDialog("Storage data was successfully imported to the CMR.", -1); + } + }); + monitor.done(); + return Status.OK_STATUS; + } + }; + importStorageJob.setUser(true); + importStorageJob.setProperty(IProgressConstants.ICON_PROPERTY, InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_IMPORT)); + importStorageJob.schedule(); + } + + return true; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/ManageLabelWizard.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/ManageLabelWizard.java new file mode 100644 index 000000000..0ccf6b7a5 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/ManageLabelWizard.java @@ -0,0 +1,93 @@ +package info.novatec.inspectit.rcp.wizard; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.wizard.page.ManageLabelWizardPage; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.label.management.AbstractLabelManagementAction; + +import java.util.List; + +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IWorkbench; + +/** + * Manage Labels wizard. + * + * @author Ivan Senic + * + */ +public class ManageLabelWizard extends Wizard implements INewWizard { + + /** + * CMR to manage labels for. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * Page. + */ + private ManageLabelWizardPage manageLabelsPage; + + /** + * Default constructor. + * + * @param cmrRepositoryDefinition + * Repository to manage labels for. + */ + public ManageLabelWizard(CmrRepositoryDefinition cmrRepositoryDefinition) { + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + this.setWindowTitle("Manage Labels"); + this.setDefaultPageImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_WIZBAN_LABEL)); + } + + /** + * {@inheritDoc} + */ + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + } + + /** + * {@inheritDoc} + */ + @Override + public void addPages() { + manageLabelsPage = new ManageLabelWizardPage(cmrRepositoryDefinition); + addPage(manageLabelsPage); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean performFinish() { + List actions = manageLabelsPage.getManagementActions(); + if (!actions.isEmpty()) { + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + try { + cmrRepositoryDefinition.getStorageService().executeLabelManagementActions(actions); + } catch (StorageException e) { + InspectIT.getDefault().createErrorDialog("There was an exception trying to execute label management operation.", e, -1); + } + } else { + InspectIT.getDefault().createInfoDialog("Can not execute label management operation, selected CMR repository is offline.", -1); + } + } + return true; + } + + /** + * Gets {@link #shouldRefreshStorages}. + * + * @return {@link #shouldRefreshStorages} + */ + public boolean isShouldRefreshStorages() { + return manageLabelsPage.isShouldRefreshStorages(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/StartRecordingWizard.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/StartRecordingWizard.java new file mode 100644 index 000000000..c1e0156bf --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/StartRecordingWizard.java @@ -0,0 +1,311 @@ +package info.novatec.inspectit.rcp.wizard; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.provider.IStorageDataProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.wizard.page.AddStorageLabelWizardPage; +import info.novatec.inspectit.rcp.wizard.page.DefineDataProcessorsWizardPage; +import info.novatec.inspectit.rcp.wizard.page.DefineNewStorageWizzardPage; +import info.novatec.inspectit.rcp.wizard.page.DefineTimelineWizardPage; +import info.novatec.inspectit.rcp.wizard.page.NewOrExistsingStorageWizardPage; +import info.novatec.inspectit.rcp.wizard.page.SelectAgentsWizardPage; +import info.novatec.inspectit.rcp.wizard.page.SelectExistingStorageWizardPage; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageException; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.processor.AbstractDataProcessor; +import info.novatec.inspectit.storage.processor.impl.AgentFilterDataProcessor; +import info.novatec.inspectit.storage.recording.RecordingProperties; +import info.novatec.inspectit.storage.recording.RecordingState; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IWorkbench; + +/** + * Wizard for starting a recording. + * + * @author Ivan Senic + * + */ +public class StartRecordingWizard extends Wizard implements INewWizard { + + /** + * {@link NewOrExistsingStorageWizardPage}. + */ + private NewOrExistsingStorageWizardPage newOrExistsingStorageWizardPage; + + /** + * Define data page. + */ + private DefineDataProcessorsWizardPage defineDataPage; + + /** + * Recording storage selection page. + */ + private SelectExistingStorageWizardPage selectStorageWizardPage; + + /** + * New storage page. + */ + private DefineNewStorageWizzardPage defineNewStorageWizzardPage; + + /** + * Select agents wizard page. + */ + private SelectAgentsWizardPage selectAgentsWizardPage; + + /** + * Time-line wizard page. + */ + private DefineTimelineWizardPage timelineWizardPage; + + /** + * Add new label wizard page. + */ + private AddStorageLabelWizardPage addLabelWizardPage; + + /** + * Initially selected CMR. + */ + private CmrRepositoryDefinition selectedCmr; + + /** + * The collection of agents that will be automatically selected in the + * {@link SelectExistingStorageWizardPage}. + */ + private Collection autoSelectedAgents; + + /** + * Recording properties defined in the wizard. + */ + private RecordingProperties recordingProperties; + + /** + * Public constructor. + */ + public StartRecordingWizard() { + this.setWindowTitle("Start Recording Wizard"); + this.setDefaultPageImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_WIZBAN_RECORD)); + } + + /** + * This constructor will extract the {@link CmrRepositoryDefinition} out of + * {@link IStorageDataProvider}. + * + * @param storageDataProvider + * {@link IStorageDataProvider}. + */ + public StartRecordingWizard(IStorageDataProvider storageDataProvider) { + this(); + this.selectedCmr = storageDataProvider.getCmrRepositoryDefinition(); + } + + /** + * This constructor gets the selected {@link CmrRepositoryDefinition}. + * + * @param cmrRepositoryDefinition + * Selected {@link CmrRepositoryDefinition}. + */ + public StartRecordingWizard(CmrRepositoryDefinition cmrRepositoryDefinition) { + this(cmrRepositoryDefinition, Collections. emptyList()); + } + + /** + * The constructor sets the CMR and provides option to define the collection of agents that will + * be automatically selected in the {@link SelectExistingStorageWizardPage}. + * + * @param cmrRepositoryDefinition + * Selected {@link CmrRepositoryDefinition}. + * @param autoSelectedAgents + * The collection of agents that will be automatically selected in the + * {@link SelectExistingStorageWizardPage}. + */ + public StartRecordingWizard(CmrRepositoryDefinition cmrRepositoryDefinition, Collection autoSelectedAgents) { + this(); + this.selectedCmr = cmrRepositoryDefinition; + this.autoSelectedAgents = autoSelectedAgents; + } + + /** + * {@inheritDoc} + */ + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + } + + /** + * {@inheritDoc} + */ + @Override + public void addPages() { + newOrExistsingStorageWizardPage = new NewOrExistsingStorageWizardPage(); + addPage(newOrExistsingStorageWizardPage); + defineNewStorageWizzardPage = new DefineNewStorageWizzardPage(selectedCmr); + addPage(defineNewStorageWizzardPage); + selectStorageWizardPage = new SelectExistingStorageWizardPage(selectedCmr, true); + addPage(selectStorageWizardPage); + selectAgentsWizardPage = new SelectAgentsWizardPage("Select Agent(s) that should participate in recording", autoSelectedAgents); + addPage(selectAgentsWizardPage); + defineDataPage = new DefineDataProcessorsWizardPage(DefineDataProcessorsWizardPage.BUFFER_DATA | DefineDataProcessorsWizardPage.SYSTEM_DATA + | DefineDataProcessorsWizardPage.EXTRACT_INVOCATIONS); + addPage(defineDataPage); + timelineWizardPage = new DefineTimelineWizardPage("Limit Recording", "Optionally select how long recording should last", DefineTimelineWizardPage.FUTURE | DefineTimelineWizardPage.BOTH_DATES); + addPage(timelineWizardPage); + addLabelWizardPage = new AddStorageLabelWizardPage(selectedCmr); + addPage(addLabelWizardPage); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean performFinish() { + StorageData storageData; + CmrRepositoryDefinition cmrRepositoryDefinition; + boolean autoFinalize; + + if (newOrExistsingStorageWizardPage.useNewStorage()) { + storageData = defineNewStorageWizzardPage.getStorageData(); + cmrRepositoryDefinition = defineNewStorageWizzardPage.getSelectedRepository(); + autoFinalize = defineNewStorageWizzardPage.isAutoFinalize(); + } else { + storageData = selectStorageWizardPage.getSelectedStorageData(); + cmrRepositoryDefinition = selectStorageWizardPage.getSelectedRepository(); + autoFinalize = selectStorageWizardPage.isAutoFinalize(); + } + + List recordingProcessors = defineDataPage.getProcessorList(); + if (!selectAgentsWizardPage.isAllAgents()) { + Set agentsIds = new HashSet(selectAgentsWizardPage.getSelectedAgents()); + AgentFilterDataProcessor agentFilterDataProcessor = new AgentFilterDataProcessor(recordingProcessors, agentsIds); + recordingProcessors = new ArrayList(1); + recordingProcessors.add(agentFilterDataProcessor); + } + + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + recordingProperties = new RecordingProperties(); + recordingProperties.setRecordingDataProcessors(recordingProcessors); + recordingProperties.setAutoFinalize(autoFinalize); + if (timelineWizardPage.isTimerframeUsed()) { + Date recordStartDate = timelineWizardPage.getFromDate(); + Date recordEndDate = timelineWizardPage.getToDate(); + Date now = new Date(); + if (null != recordStartDate && recordStartDate.after(now)) { + recordingProperties.setStartDelay(recordStartDate.getTime() - now.getTime()); + } + if (null != recordEndDate && recordEndDate.after(now)) { + if (null != recordStartDate && recordStartDate.before(recordEndDate)) { + recordingProperties.setRecordDuration(recordEndDate.getTime() - recordStartDate.getTime()); + } else { + recordingProperties.setRecordDuration(recordEndDate.getTime() - now.getTime()); + } + } + } + boolean canStart = cmrRepositoryDefinition.getStorageService().getRecordingState() == RecordingState.OFF; + if (canStart) { + try { + StorageData recordingStorage = cmrRepositoryDefinition.getStorageService().startOrScheduleRecording(storageData, recordingProperties); + List> labels = addLabelWizardPage.getLabelsToAdd(); + if (!labels.isEmpty()) { + cmrRepositoryDefinition.getStorageService().addLabelsToStorage(recordingStorage, labels, true); + } + } catch (StorageException e) { + InspectIT.getDefault().createErrorDialog("Recording did not start.", e, -1); + return false; + } + } + } else { + InspectIT.getDefault().createErrorDialog("Recording did not start. Selected CMR repository is currently not available.", -1); + return false; + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public IWizardPage getNextPage(IWizardPage page) { + if (ObjectUtils.equals(page, newOrExistsingStorageWizardPage)) { + if (newOrExistsingStorageWizardPage.useNewStorage()) { + return defineNewStorageWizzardPage; + } else { + return selectStorageWizardPage; + } + } else if (ObjectUtils.equals(page, defineNewStorageWizzardPage)) { + selectAgentsWizardPage.setCmrRepositoryDefinition(defineNewStorageWizzardPage.getSelectedRepository()); + addLabelWizardPage.setStorageData(defineNewStorageWizzardPage.getStorageData()); + return selectAgentsWizardPage; + } else if (ObjectUtils.equals(page, selectStorageWizardPage)) { + selectAgentsWizardPage.setCmrRepositoryDefinition(selectStorageWizardPage.getSelectedRepository()); + addLabelWizardPage.setStorageData(selectStorageWizardPage.getSelectedStorageData()); + return selectAgentsWizardPage; + } else { + return super.getNextPage(page); + } + } + + /** + * {@inheritDoc} + */ + @Override + public IWizardPage getPreviousPage(IWizardPage page) { + if (ObjectUtils.equals(page, defineNewStorageWizzardPage) || ObjectUtils.equals(page, selectStorageWizardPage)) { + return newOrExistsingStorageWizardPage; + } else if (ObjectUtils.equals(page, selectAgentsWizardPage)) { + if (newOrExistsingStorageWizardPage.useNewStorage()) { + return defineNewStorageWizzardPage; + } else { + return selectStorageWizardPage; + } + } else { + return super.getPreviousPage(page); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canFinish() { + if (!newOrExistsingStorageWizardPage.isPageComplete()) { + return false; + } else if (newOrExistsingStorageWizardPage.useNewStorage() && !defineNewStorageWizzardPage.isPageComplete()) { + return false; + } else if (!newOrExistsingStorageWizardPage.useNewStorage() && !selectStorageWizardPage.isPageComplete()) { + return false; + } else if (!selectAgentsWizardPage.isPageComplete()) { + return false; + } else if (!defineDataPage.isPageComplete()) { + return false; + } else if (!timelineWizardPage.isPageComplete()) { + return false; + } + return true; + } + + /** + * Gets {@link #recordingProperties}. + * + * @return {@link #recordingProperties} + */ + public RecordingProperties getRecordingProperties() { + return recordingProperties; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/UploadStorageWizard.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/UploadStorageWizard.java new file mode 100644 index 000000000..9e41ca97a --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/UploadStorageWizard.java @@ -0,0 +1,110 @@ +package info.novatec.inspectit.rcp.wizard; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.provider.ILocalStorageDataProvider; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.storage.InspectITStorageManager; +import info.novatec.inspectit.rcp.view.impl.StorageManagerView; +import info.novatec.inspectit.rcp.wizard.page.UploadStorageWizardPage; +import info.novatec.inspectit.storage.LocalStorageData; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.progress.IProgressConstants; + +/** + * Wizard for uploading a storage. + * + * @author Ivan Senic + * + */ +public class UploadStorageWizard extends Wizard implements INewWizard { + + /** + * Storage to be uploaded. + */ + private LocalStorageData localStorageData; + + /** + * Wizard page. + */ + private UploadStorageWizardPage uploadStorageWizardPage; + + /** + * Default constructor. + * + * @param localStorageDataProvider + * {@link ILocalStorageDataProvider} pointing to the storage to upload. + */ + public UploadStorageWizard(ILocalStorageDataProvider localStorageDataProvider) { + this.localStorageData = localStorageDataProvider.getLocalStorageData(); + this.setWindowTitle("Upload Storage to CMR (Central Management Repository)"); + this.setDefaultPageImageDescriptor(InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_WIZBAN_UPLOAD)); + } + + /** + * {@inheritDoc} + */ + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + } + + /** + * {@inheritDoc} + */ + @Override + public void addPages() { + uploadStorageWizardPage = new UploadStorageWizardPage(localStorageData); + addPage(uploadStorageWizardPage); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean performFinish() { + final CmrRepositoryDefinition cmrRepositoryDefinition = uploadStorageWizardPage.getCmrRepositoryDefinition(); + Job uploadStorageJob = new Job("Upload storage") { + + @Override + protected IStatus run(IProgressMonitor monitor) { + SubMonitor subMonitor = SubMonitor.convert(monitor); + InspectITStorageManager storageManager = InspectIT.getDefault().getInspectITStorageManager(); + try { + storageManager.uploadCompleteStorage(localStorageData, cmrRepositoryDefinition, subMonitor); + cmrRepositoryDefinition.getStorageService().createStorageFromUploadedDir(localStorageData); + } catch (final Exception e) { + return new Status(Status.ERROR, InspectIT.ID, "Exception occurred during storage upload", e); + } + + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + InspectIT.getDefault().createInfoDialog("Selected storage successfully uploaded.", -1); + IViewPart storageManagerView = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().findView(StorageManagerView.VIEW_ID); + if (storageManagerView instanceof StorageManagerView) { + ((StorageManagerView) storageManagerView).refresh(cmrRepositoryDefinition); + } + } + }); + return Status.OK_STATUS; + } + }; + uploadStorageJob.setUser(true); + uploadStorageJob.setProperty(IProgressConstants.ICON_PROPERTY, InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_STORAGE_UPLOAD)); + uploadStorageJob.schedule(); + return true; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/AddStorageLabelWizardPage.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/AddStorageLabelWizardPage.java new file mode 100644 index 000000000..8a79efd12 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/AddStorageLabelWizardPage.java @@ -0,0 +1,501 @@ +package info.novatec.inspectit.rcp.wizard.page; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.ImageFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.storage.label.edit.LabelValueEditingSupport; +import info.novatec.inspectit.rcp.storage.label.edit.LabelValueEditingSupport.LabelEditListener; +import info.novatec.inspectit.rcp.wizard.ManageLabelWizard; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.label.BooleanStorageLabel; +import info.novatec.inspectit.storage.label.DateStorageLabel; +import info.novatec.inspectit.storage.label.NumberStorageLabel; +import info.novatec.inspectit.storage.label.StringStorageLabel; +import info.novatec.inspectit.storage.label.type.AbstractStorageLabelType; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Table; + +/** + * Page for adding storage labels. + * + * @author Ivan Senic + * + */ +public class AddStorageLabelWizardPage extends WizardPage { + + /** + * {@link StyledString} that has empty string. + */ + protected static final StyledString EMPTY_STYLED_STRING = new StyledString(); + + /** + * Defines if the data needed to be entered on this wizard page is optional. If it is optional + * the page {@link #isPageComplete()} method will return true always, so that wizards can finish + * although no label was inserted. + */ + private boolean optional; + + /** + * Default message. + */ + private String defaultMessage = "Define the new label type and its value"; + + /** + * {@link StorageData} to add label to. + */ + private StorageData storageData; + + /** + * {@link CmrRepositoryDefinition} where data is located. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * List of available labels types. + */ + private List> labelTypeList; + + /** + * List of labels to add. + */ + private List> labelsToAdd = new ArrayList>(); + + /** + * Widgets. + */ + private Combo labelTypeSelection; + + /** + * Table to display labels. + */ + private TableViewer labelsTableViewer; + + /** + * Main composite. + */ + private Composite main; + + /** + * Remove label button. + */ + private Button removeLabelButton; + + /** + * Page complete listener. + */ + private Listener pageCompleteListener = new Listener() { + + @Override + public void handleEvent(Event event) { + setPageComplete(isPageComplete()); + } + }; + + /** + * Value column. + */ + private TableViewerColumn value; + + /** + * Default constructor. Use this constructor when storage data is known. Note that using the + * page this way, {@link #optional} value is set to true, thus wizard page would not allow to + * finish until label is correctly created. + * + * @param storageData + * {@link StorageData} + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} + */ + public AddStorageLabelWizardPage(StorageData storageData, CmrRepositoryDefinition cmrRepositoryDefinition) { + super("Add New Labels"); + this.setTitle("Add New Labels"); + defaultMessage += " for the storage '" + storageData.getName() + "'"; + this.setMessage(defaultMessage); + this.storageData = storageData; + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + this.optional = false; + labelTypeList = cmrRepositoryDefinition.getStorageService().getAllLabelTypes(); + } + + /** + * The constructor for usage when storage to label is not known in advance. Note that using this + * constructor will set {@link #optional} to true. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} + */ + public AddStorageLabelWizardPage(CmrRepositoryDefinition cmrRepositoryDefinition) { + super("Add New Labels"); + this.setTitle("Add New Labels"); + defaultMessage = "Optionally add one or more labels to the selected storage"; + this.setMessage(defaultMessage); + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + this.optional = true; + + } + + /** + * {@inheritDoc} + */ + @Override + public void createControl(Composite parent) { + main = new Composite(parent, SWT.NONE); + main.setLayout(new GridLayout(4, false)); + + Label l = new Label(main, SWT.NONE); + l.setText("Label type:"); + l.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); + + labelTypeSelection = new Combo(main, SWT.READ_ONLY); + labelTypeSelection.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + final Button addButton = new Button(main, SWT.PUSH); + addButton.setEnabled(false); + addButton.setText("Add"); + addButton.setToolTipText("Add Label"); + addButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + addButton.addSelectionListener(new SelectionAdapter() { + @SuppressWarnings("unchecked") + @Override + public void widgetSelected(SelectionEvent e) { + int index = labelTypeSelection.getSelectionIndex(); + if (-1 != index) { + AbstractStorageLabelType selectedLabelType = labelTypeList.get(index); + + if (selectedLabelType.isOnePerStorage() && isLabelTypePresent(selectedLabelType)) { + setMessage("Selected label type is one-per-storage. New label value will overwrite the old one.", IMessageProvider.WARNING); + Iterator> it = labelsToAdd.iterator(); + while (it.hasNext()) { + if (Objects.equals(it.next().getStorageLabelType(), selectedLabelType)) { + it.remove(); + break; + } + } + } else { + setMessage(defaultMessage); + } + + if (selectedLabelType.getValueClass().equals(Boolean.class)) { + BooleanStorageLabel booleanStorageLabel = new BooleanStorageLabel(); + booleanStorageLabel.setStorageLabelType((AbstractStorageLabelType) selectedLabelType); + labelsToAdd.add(booleanStorageLabel); + } else if (selectedLabelType.getValueClass().equals(Date.class)) { + DateStorageLabel dateStorageLabel = new DateStorageLabel(); + dateStorageLabel.setStorageLabelType((AbstractStorageLabelType) selectedLabelType); + labelsToAdd.add(dateStorageLabel); + } else if (selectedLabelType.getValueClass().equals(Number.class)) { + NumberStorageLabel numberStorageLabel = new NumberStorageLabel(); + numberStorageLabel.setStorageLabelType((AbstractStorageLabelType) selectedLabelType); + labelsToAdd.add(numberStorageLabel); + } else if (selectedLabelType.getValueClass().equals(String.class)) { + StringStorageLabel stringStorageLabel = new StringStorageLabel(); + stringStorageLabel.setStorageLabelType((AbstractStorageLabelType) selectedLabelType); + labelsToAdd.add(stringStorageLabel); + } + labelsTableViewer.refresh(); + labelsTableViewer.editElement(labelsToAdd.get(labelsToAdd.size() - 1), 1); + } + } + }); + + labelTypeSelection.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + addButton.setEnabled(labelTypeSelection.getSelectionIndex() >= 0); + } + }); + + Button manageLabelsButton = new Button(main, SWT.PUSH); + manageLabelsButton.setImage(InspectIT.getDefault().getImage(InspectITImages.IMG_LABEL)); + manageLabelsButton.setToolTipText("Manage Labels on Repository"); + manageLabelsButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + manageLabelsButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + ManageLabelWizard manageLabelWizard = new ManageLabelWizard(cmrRepositoryDefinition); + WizardDialog wizardDialog = new WizardDialog(getShell(), manageLabelWizard); + wizardDialog.open(); + if (wizardDialog.getReturnCode() == WizardDialog.OK) { + updateLabelTypes(); + } + } + + }); + + // table + createLabelTable(main); + GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true, 4, 1); + gd.minimumHeight = 100; + labelsTableViewer.getTable().setLayoutData(gd); + + addButton.addListener(SWT.Selection, pageCompleteListener); + labelsTableViewer.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + removeLabelButton.setEnabled(!labelsTableViewer.getSelection().isEmpty()); + } + }); + + labelsTableViewer.getTable().addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.keyCode == SWT.DEL) { + deleteSelectedlabels(); + } + } + }); + + // empty help label + gd = new GridData(); + gd.horizontalSpan = 2; + new Label(main, SWT.NONE).setLayoutData(gd); + + removeLabelButton = new Button(main, SWT.PUSH); + removeLabelButton.setText("Remove"); + removeLabelButton.setToolTipText("Remove Selected Labels"); + removeLabelButton.setEnabled(false); + removeLabelButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 2, 1)); + removeLabelButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + deleteSelectedlabels(); + } + + }); + + updateLabelTypes(); + setControl(main); + } + + /** + * Updates the labels types available on the server. + * + */ + private void updateLabelTypes() { + Job updateLabelsTypes = new Job("Loading Label Types") { + @Override + protected IStatus run(IProgressMonitor monitor) { + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + labelTypeList = cmrRepositoryDefinition.getStorageService().getAllLabelTypes(); + } else { + labelTypeList = Collections.emptyList(); + } + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + labelTypeSelection.removeAll(); + for (AbstractStorageLabelType labelType : labelTypeList) { + labelTypeSelection.add(TextFormatter.getLabelName(labelType)); + } + } + }); + return Status.OK_STATUS; + } + }; + updateLabelsTypes.schedule(); + } + + /** + * Deletes selected labels. + */ + private void deleteSelectedlabels() { + StructuredSelection structuredSelection = (StructuredSelection) labelsTableViewer.getSelection(); + if (!structuredSelection.isEmpty()) { + for (Iterator it = structuredSelection.iterator(); it.hasNext();) { + labelsToAdd.remove(it.next()); + } + labelsTableViewer.refresh(); + setPageComplete(isPageComplete()); + } + } + + /** + * Creates the table for the labels. + * + * @param parent + * Parent composite. + */ + private void createLabelTable(Composite parent) { + Table table = new Table(parent, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.VIRTUAL | SWT.FULL_SELECTION); + table.setHeaderVisible(true); + + labelsTableViewer = new TableViewer(table); + + TableViewerColumn type = new TableViewerColumn(labelsTableViewer, SWT.NONE); + type.getColumn().setText("Label"); + type.getColumn().setMoveable(false); + type.getColumn().setResizable(true); + type.getColumn().setWidth(200); + + value = new TableViewerColumn(labelsTableViewer, SWT.NONE); + value.getColumn().setText("Value"); + value.getColumn().setMoveable(false); + value.getColumn().setResizable(true); + value.getColumn().setWidth(100); + LabelValueEditingSupport editingSupport = new LabelValueEditingSupport(labelsTableViewer, storageData, cmrRepositoryDefinition); + editingSupport.addLabelEditListener(new LabelEditListener() { + @Override + public void preLabelValueChange(AbstractStorageLabel label) { + } + + @Override + public void postLabelValueChange(AbstractStorageLabel label) { + setPageComplete(isPageComplete()); + } + + }); + value.setEditingSupport(editingSupport); + + labelsTableViewer.setContentProvider(new ArrayContentProvider()); + labelsTableViewer.setLabelProvider(new StyledCellIndexLabelProvider() { + @Override + protected StyledString getStyledText(Object element, int index) { + if (element instanceof AbstractStorageLabel) { + AbstractStorageLabel label = (AbstractStorageLabel) element; + switch (index) { + case 0: + return new StyledString(TextFormatter.getLabelName(label)); + case 1: + if (null != label.getValue()) { + return new StyledString(TextFormatter.getLabelValue(label, false)); + } + default: + break; + } + } + return EMPTY_STYLED_STRING; + } + + @Override + protected Image getColumnImage(Object element, int index) { + if (index == 0 && element instanceof AbstractStorageLabel) { + return ImageFormatter.getImageForLabel(((AbstractStorageLabel) element).getStorageLabelType()); + } + return null; + } + }); + + labelsTableViewer.setInput(labelsToAdd); + + } + + /** + * Returns if the label type is present in the storage or in the list of labels to add. + * + * @param selectedLabelType + * Selected {@link AbstractStorageLabelType}. + * @return True if label type already exists. + */ + private boolean isLabelTypePresent(AbstractStorageLabelType selectedLabelType) { + if (storageData.isLabelPresent(selectedLabelType)) { + return true; + } + for (AbstractStorageLabel label : labelsToAdd) { + if (ObjectUtils.equals(selectedLabelType, label.getStorageLabelType())) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPageComplete() { + if (optional) { + return true; + } else if (!labelsToAdd.isEmpty()) { + for (AbstractStorageLabel label : labelsToAdd) { + if (null != label.getValue()) { + return true; + } + } + } + return false; + } + + /** + * Gets {@link #labelsToAdd}. + * + * @return {@link #labelsToAdd} + */ + public List> getLabelsToAdd() { + Iterator> it = labelsToAdd.iterator(); + while (it.hasNext()) { + if (null == it.next().getValue()) { + it.remove(); + } + } + + return labelsToAdd; + } + + /** + * Sets {@link #storageData}. + * + * @param storageData + * New value for {@link #storageData} + */ + public void setStorageData(StorageData storageData) { + this.storageData = storageData; + // update the editing support + LabelValueEditingSupport editingSupport = new LabelValueEditingSupport(labelsTableViewer, storageData, cmrRepositoryDefinition); + editingSupport.addLabelEditListener(new LabelEditListener() { + @Override + public void preLabelValueChange(AbstractStorageLabel label) { + } + + @Override + public void postLabelValueChange(AbstractStorageLabel label) { + setPageComplete(isPageComplete()); + } + + }); + value.setEditingSupport(editingSupport); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/DefineCmrWizardPage.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/DefineCmrWizardPage.java new file mode 100644 index 000000000..05993ecd7 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/DefineCmrWizardPage.java @@ -0,0 +1,248 @@ +package info.novatec.inspectit.rcp.wizard.page; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Text; + +/** + * Wizard page for definition of the new or existing {@link CmrRepositoryDefinition}. + * + * @author Ivan Senic + * + */ +public class DefineCmrWizardPage extends WizardPage { + + /** + * Default page message. + */ + private static final String DEFAULT_MESSAGE = "Define the information for the CMR Repository"; + + /** + * Name tex box. + */ + private Text nameBox; + + /** + * IP tex box. + */ + private Text ipBox; + + /** + * Port tex box. + */ + private Text portBox; + + /** + * Description box. + */ + private Text descriptionBox; + + /** + * Repository to edit if edit mode is on. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * List of existing repositories to check if the same one already exists. + */ + private List existingRepositories; + + /** + * Default constructor. + * + * @param title + * title for the wizard page + */ + public DefineCmrWizardPage(String title) { + this(title, null); + } + + /** + * Secondary constructor for editing existing CMR. + * + * @param title + * title for the wizard page + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to edit + * + */ + public DefineCmrWizardPage(String title, CmrRepositoryDefinition cmrRepositoryDefinition) { + super(title); + this.setTitle(title); + this.setMessage(DEFAULT_MESSAGE); + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + this.existingRepositories = new ArrayList(InspectIT.getDefault().getCmrRepositoryManager().getCmrRepositoryDefinitions()); + if (null != cmrRepositoryDefinition) { + this.existingRepositories.remove(cmrRepositoryDefinition); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void createControl(Composite parent) { + final Composite main = new Composite(parent, SWT.NONE); + main.setLayout(new GridLayout(4, false)); + + Label nameLabel = new Label(main, SWT.LEFT); + nameLabel.setText("Server name:"); + nameBox = new Text(main, SWT.BORDER); + nameBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1)); + + Label ipLabel = new Label(main, SWT.LEFT); + ipLabel.setText("IP Address:"); + ipBox = new Text(main, SWT.BORDER); + ipBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + ipBox.setText(CmrRepositoryDefinition.DEFAULT_IP); + + Label portLabel = new Label(main, SWT.LEFT); + portLabel.setText("Port:"); + portBox = new Text(main, SWT.BORDER | SWT.RIGHT); + GridData gd = new GridData(SWT.FILL, SWT.FILL, false, false); + portBox.setLayoutData(gd); + portBox.setText(String.valueOf(CmrRepositoryDefinition.DEFAULT_PORT)); + portBox.setTextLimit(5); + portBox.addListener(SWT.Modify, new Listener() { + @Override + public void handleEvent(Event event) { + if (portBox.getText().length() > String.valueOf(CmrRepositoryDefinition.DEFAULT_PORT).length()) { + main.layout(); + } + } + }); + + Label descLabel = new Label(main, SWT.LEFT); + descLabel.setText("Description:"); + descLabel.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + descriptionBox = new Text(main, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.WRAP); + gd = new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1); + gd.widthHint = 300; + descriptionBox.setLayoutData(gd); + + Listener pageCompletionListener = new Listener() { + + @Override + public void handleEvent(Event event) { + setPageComplete(isPageComplete()); + setPageMessage(); + } + }; + + nameBox.addListener(SWT.Modify, pageCompletionListener); + ipBox.addListener(SWT.Modify, pageCompletionListener); + portBox.addListener(SWT.Modify, pageCompletionListener); + + if (null != cmrRepositoryDefinition) { + nameBox.setText(cmrRepositoryDefinition.getName()); + ipBox.setText(cmrRepositoryDefinition.getIp()); + portBox.setText(String.valueOf(cmrRepositoryDefinition.getPort())); + descriptionBox.setText(TextFormatter.emptyStringIfNull(cmrRepositoryDefinition.getDescription())); + } + + setControl(main); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canFlipToNextPage() { + return isPageComplete(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPageComplete() { + if (nameBox.getText().isEmpty()) { + return false; + } + if (ipBox.getText().trim().isEmpty()) { + return false; + } + if (portBox.getText().trim().isEmpty()) { + return false; + } else { + try { + Integer.parseInt(portBox.getText().trim()); + } catch (NumberFormatException e) { + return false; + } + } + + String ip = ipBox.getText().trim(); + int port = Integer.parseInt(portBox.getText().trim()); + for (CmrRepositoryDefinition cmrRepositoryDefinition : existingRepositories) { + if (Objects.equals(ip, cmrRepositoryDefinition.getIp()) && port == cmrRepositoryDefinition.getPort()) { + return false; + } + } + return true; + } + + /** + * @return Returns the defined {@link CmrRepositoryDefinition}. + */ + public CmrRepositoryDefinition getCmrRepositoryDefinition() { + CmrRepositoryDefinition cmrRepositoryDefinition = new CmrRepositoryDefinition(ipBox.getText().trim(), Integer.parseInt(portBox.getText()), nameBox.getText().trim()); + if (!descriptionBox.getText().trim().isEmpty()) { + cmrRepositoryDefinition.setDescription(descriptionBox.getText().trim()); + } else { + cmrRepositoryDefinition.setDescription(""); + } + return cmrRepositoryDefinition; + } + + /** + * Sets the message based on the page selections. + */ + private void setPageMessage() { + if (nameBox.getText().isEmpty()) { + setMessage("No value for the CMR name entered", ERROR); + return; + } + if (ipBox.getText().trim().isEmpty()) { + setMessage("No value for the CMR IP address entered", ERROR); + return; + } + if (portBox.getText().trim().isEmpty()) { + setMessage("No value for the CMR port entered", ERROR); + return; + } else { + try { + Integer.parseInt(portBox.getText().trim()); + } catch (NumberFormatException e) { + setMessage("The port is not in a valid number format", ERROR); + return; + } + } + + String ip = ipBox.getText().trim(); + int port = Integer.parseInt(portBox.getText().trim()); + for (CmrRepositoryDefinition cmrRepositoryDefinition : existingRepositories) { + if (Objects.equals(ip, cmrRepositoryDefinition.getIp()) && port == cmrRepositoryDefinition.getPort()) { + setMessage("The repository with given IP address and port already exists", ERROR); + return; + } + } + + setMessage(DEFAULT_MESSAGE); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/DefineDataProcessorsWizardPage.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/DefineDataProcessorsWizardPage.java new file mode 100644 index 000000000..c00728b47 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/DefineDataProcessorsWizardPage.java @@ -0,0 +1,492 @@ +package info.novatec.inspectit.rcp.wizard.page; + +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.ClassLoadingInformationData; +import info.novatec.inspectit.communication.data.CompilationInformationData; +import info.novatec.inspectit.communication.data.CpuInformationData; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.MemoryInformationData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.SystemInformationData; +import info.novatec.inspectit.communication.data.ThreadInformationData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.indexing.aggregation.impl.SqlStatementDataAggregator; +import info.novatec.inspectit.indexing.aggregation.impl.TimerDataAggregator; +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.storage.processor.AbstractDataProcessor; +import info.novatec.inspectit.storage.processor.impl.DataAggregatorProcessor; +import info.novatec.inspectit.storage.processor.impl.DataSaverProcessor; +import info.novatec.inspectit.storage.processor.impl.InvocationClonerDataProcessor; +import info.novatec.inspectit.storage.processor.impl.InvocationExtractorDataProcessor; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Spinner; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; + +/** + * Wizard page where data that will be saved in the storage is saved. + * + * @author Ivan Senic + * + */ +public class DefineDataProcessorsWizardPage extends WizardPage { + + /** + * Marker for having buffer data available for selection. + */ + public static final int BUFFER_DATA = 1; + + /** + * Marker for having system data available for selection. + */ + public static final int SYSTEM_DATA = 2; + + /** + * Marker for extract from invocations available for selection. + */ + public static final int EXTRACT_INVOCATIONS = 4; + + /** + * Marker that marks that only invocations are saved. + */ + public static final int ONLY_INVOCATIONS = 8; + + /** + * Marker that marks that only timer are saved. + */ + public static final int ONLY_TIMERS = 16; + + /** + * Marker that marks that only invocations are saved. + */ + public static final int ONLY_SQL_STATEMENTS = 32; + + /** + * Marker that marks that only HTTP timers are saved. + */ + public static final int ONLY_HTTP_TIMERS = 64; + + /** + * Marker that marks that only exceptions are saved. + */ + public static final int ONLY_EXCEPTIONS = 128; + + /** + * Default message. + */ + private static final String DEFAULT_MESSAGE = "Define the data that should be stored in the storage and additional options"; + + /** + * Input list for table containing all the classes. + */ + private Set> inputList = new HashSet>(); + + /** + * Style for providing different selection possibilities. + */ + private int selectionStyle; + + /** + * Table where all data types are displayed. + */ + private Table table; + + /** + * Spinner for the aggregation period. + */ + private Spinner aggregationPeriodSpiner; + + /** + * Page completed listener. + */ + private Listener pageCompleteListener = new Listener() { + @Override + public void handleEvent(Event event) { + setPageComplete(isPageComplete()); + } + }; + + /** + * Default constructor. + * + * @param selectionStyle + * Combination of styles for this page in the SWT way. + * @see #BUFFER_DATA + * @see #SYSTEM_DATA + * @see #EXTRACT_INVOCATIONS + */ + public DefineDataProcessorsWizardPage(int selectionStyle) { + super("Define Data"); + setTitle("Define Data"); + setDescription(DEFAULT_MESSAGE); + this.selectionStyle = selectionStyle; + if (isStyleApplied(BUFFER_DATA) || isStyleApplied(ONLY_INVOCATIONS)) { + inputList.add(TimerData.class); + inputList.add(HttpTimerData.class); + inputList.add(SqlStatementData.class); + inputList.add(InvocationSequenceData.class); + inputList.add(ExceptionSensorData.class); + } + if (isStyleApplied(ONLY_TIMERS)) { + inputList.add(TimerData.class); + inputList.add(InvocationSequenceData.class); + } + if (isStyleApplied(ONLY_SQL_STATEMENTS)) { + inputList.add(SqlStatementData.class); + inputList.add(InvocationSequenceData.class); + } + if (isStyleApplied(ONLY_HTTP_TIMERS)) { + inputList.add(HttpTimerData.class); + inputList.add(InvocationSequenceData.class); + } + if (isStyleApplied(ONLY_EXCEPTIONS)) { + inputList.add(ExceptionSensorData.class); + inputList.add(InvocationSequenceData.class); + } + if (isStyleApplied(SYSTEM_DATA)) { + inputList.add(MemoryInformationData.class); + inputList.add(CpuInformationData.class); + inputList.add(ClassLoadingInformationData.class); + inputList.add(CompilationInformationData.class); + inputList.add(ThreadInformationData.class); + inputList.add(SystemInformationData.class); + } + } + + /** + * {@inheritDoc} + */ + public void createControl(Composite parent) { + final Composite main = new Composite(parent, SWT.NONE); + main.setLayout(new GridLayout(4, false)); + + table = new Table(main, SWT.BORDER | SWT.CHECK | SWT.V_SCROLL | SWT.FULL_SELECTION); + table.setHeaderVisible(false); + table.setLinesVisible(false); + table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 2)); + + TableViewer tableViewer = new TableViewer(table); + TableViewerColumn column = new TableViewerColumn(tableViewer, SWT.LEFT); + column.getColumn().setWidth(300); + column.getColumn().setMoveable(false); + column.getColumn().setResizable(false); + column.setLabelProvider(new DataColumnLabelProvider()); + tableViewer.setContentProvider(new ArrayContentProvider()); + tableViewer.setInput(inputList); + tableViewer.refresh(); + + if (isStyleApplied(BUFFER_DATA) || isStyleApplied(SYSTEM_DATA)) { + for (TableItem tableItem : table.getItems()) { + tableItem.setChecked(true); + } + } else { + for (TableItem tableItem : table.getItems()) { + if (ObjectUtils.equals(tableItem.getData(), InvocationSequenceData.class) && isStyleApplied(ONLY_INVOCATIONS)) { + tableItem.setChecked(true); + } else if (ObjectUtils.equals(tableItem.getData(), TimerData.class) && isStyleApplied(ONLY_TIMERS)) { + tableItem.setChecked(true); + } else if (ObjectUtils.equals(tableItem.getData(), SqlStatementData.class) && isStyleApplied(ONLY_SQL_STATEMENTS)) { + tableItem.setChecked(true); + } else if (ObjectUtils.equals(tableItem.getData(), ExceptionSensorData.class) && isStyleApplied(ONLY_EXCEPTIONS)) { + tableItem.setChecked(true); + } else if (ObjectUtils.equals(tableItem.getData(), HttpTimerData.class) && isStyleApplied(ONLY_HTTP_TIMERS)) { + tableItem.setChecked(true); + } else { + tableItem.setChecked(false); + } + } + } + + Button selectAll = new Button(main, SWT.PUSH); + selectAll.setText("Select All"); + selectAll.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + for (TableItem tableItem : table.getItems()) { + tableItem.setChecked(true); + aggregationPeriodSpiner.setEnabled(true); + } + } + }); + selectAll.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + + Button deselectAll = new Button(main, SWT.PUSH); + deselectAll.setText("Deselect All"); + deselectAll.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + for (TableItem tableItem : table.getItems()) { + tableItem.setChecked(false); + aggregationPeriodSpiner.setEnabled(false); + } + } + }); + deselectAll.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + + Label info = new Label(main, SWT.WRAP); + info.setImage(JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_INFO)); + info.setToolTipText("All Timer and SQL Statement Data objects need to be aggregated before saved to the storage, because the amount of objects to be saved is in most cases too high and can impose performance problems while writing to disk. Thus, please select the aggregation period for these two data types."); + + new Label(main, SWT.NONE).setText("Aggregation period for Timer and SQL Statement Data:"); + aggregationPeriodSpiner = new Spinner(main, SWT.BORDER); + aggregationPeriodSpiner.setMinimum(5); + aggregationPeriodSpiner.setIncrement(5); + aggregationPeriodSpiner.setSelection(5); + new Label(main, SWT.NONE).setText("seconds"); + + table.addListener(SWT.Selection, new Listener() { + + @Override + public void handleEvent(Event e) { + if (e.detail == SWT.CHECK) { + aggregationPeriodSpiner.setEnabled(isSelected(TimerData.class) || isSelected(SqlStatementData.class)); + } + } + }); + table.addListener(SWT.Selection, pageCompleteListener); + selectAll.addListener(SWT.Selection, pageCompleteListener); + deselectAll.addListener(SWT.Selection, pageCompleteListener); + + setControl(main); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPageComplete() { + if (!atLeastOneTypeSelected()) { + setMessage("At least one data type has to be selected", ERROR); + return false; + } + if (isStyleApplied(ONLY_INVOCATIONS) && !getSelectedClassesFromTable().contains(InvocationSequenceData.class)) { + setMessage("Invocation Sequence Data type has to be selected because it is source of data", ERROR); + return false; + } + if (isStyleApplied(ONLY_TIMERS) && !getSelectedClassesFromTable().contains(TimerData.class)) { + setMessage("Timer Data type has to be selected because it is source of data", ERROR); + return false; + } + if (isStyleApplied(ONLY_SQL_STATEMENTS) && !getSelectedClassesFromTable().contains(SqlStatementData.class)) { + setMessage("SQL Statement Data type has to be selected because it is source of data", ERROR); + return false; + } + if (isStyleApplied(ONLY_EXCEPTIONS) && !getSelectedClassesFromTable().contains(ExceptionSensorData.class)) { + setMessage("Exception Sensor Data type has to be selected because it is source of data", ERROR); + return false; + } + if (isStyleApplied(ONLY_HTTP_TIMERS) && !getSelectedClassesFromTable().contains(HttpTimerData.class)) { + setMessage("HTTP Timer Data type has to be selected because it is source of data", ERROR); + return false; + } + setMessage(DEFAULT_MESSAGE); + return true; + } + + /** + * @return Processor list for the storage opening. + */ + public List getProcessorList() { + List normalProcessors = new ArrayList(); + + /** + * Normal saving processor. + */ + List> saveClassesList = getSelectedClassesFromTable(); + boolean writeInvocationAffiliation = saveClassesList.contains(InvocationSequenceData.class); + + if (!saveClassesList.isEmpty()) { + normalProcessors.add(new DataSaverProcessor(saveClassesList, writeInvocationAffiliation)); + } + + /** + * Aggregation. + */ + // aggregation period must be in the milliseconds, thus we multiply with 1000 + int aggregationPeriod = aggregationPeriodSpiner.getSelection() * 1000; + if (saveClassesList.contains(TimerData.class)) { + saveClassesList.remove(TimerData.class); + DataAggregatorProcessor dataAggregatorProcessor = new DataAggregatorProcessor(TimerData.class, aggregationPeriod, new TimerDataAggregator(), + writeInvocationAffiliation); + normalProcessors.add(dataAggregatorProcessor); + } + + if (saveClassesList.contains(SqlStatementData.class)) { + saveClassesList.remove(SqlStatementData.class); + DataAggregatorProcessor dataAggregatorProcessor = new DataAggregatorProcessor(SqlStatementData.class, aggregationPeriod, + new SqlStatementDataAggregator(true), writeInvocationAffiliation); + normalProcessors.add(dataAggregatorProcessor); + } + + /** + * Invocation extractor & cloner. + */ + if (saveClassesList.contains(InvocationSequenceData.class)) { + // we only include the extractor of invocations if the style is specified + if (isStyleApplied(EXTRACT_INVOCATIONS)) { + List chainedProcessorsForExtractor = new ArrayList(); + chainedProcessorsForExtractor.addAll(normalProcessors); + InvocationExtractorDataProcessor invocationExtractorDataProcessor = new InvocationExtractorDataProcessor(chainedProcessorsForExtractor); + normalProcessors.add(invocationExtractorDataProcessor); + } + normalProcessors.add(new InvocationClonerDataProcessor()); + } + + return normalProcessors; + } + + /** + * Tests if specific style is applied. + * + * @param style + * Style to test. + * @return True if style is part of style with which view was instantiated. + */ + private boolean isStyleApplied(int style) { + return (selectionStyle & style) != 0; + } + + /** + * Returns if at least one item in the table is selected. + * + * @return Returns if at least one item in the table is selected. + */ + private boolean atLeastOneTypeSelected() { + for (TableItem tableItem : table.getItems()) { + if (tableItem.getChecked()) { + return true; + } + } + return false; + } + + /** + * Returns list of selected classes in table. + * + * @return Returns list of selected classes in table. + */ + @SuppressWarnings("unchecked") + private List> getSelectedClassesFromTable() { + List> classList = new ArrayList>(); + for (TableItem tableItem : table.getItems()) { + if (tableItem.getChecked()) { + classList.add((Class) tableItem.getData()); + } + } + return classList; + } + + /** + * Returns if the class is selected in the table. + * + * @param clazz + * Class to check. + * @return True if class is selected, otherwise false. + */ + private boolean isSelected(Class clazz) { + for (TableItem tableItem : table.getItems()) { + if (ObjectUtils.equals(tableItem.getData(), clazz)) { + return tableItem.getChecked(); + } + } + return false; + } + + /** + * Label provider for only column in the table. + * + * @author Ivan Senic + * + */ + private static class DataColumnLabelProvider extends ColumnLabelProvider { + + /** + * {@inheritDoc} + */ + @Override + public String getText(Object element) { + if (ObjectUtils.equals(element, TimerData.class)) { + return "Timer Data"; + } else if (ObjectUtils.equals(element, HttpTimerData.class)) { + return "HTTP Timer Data"; + } else if (ObjectUtils.equals(element, SqlStatementData.class)) { + return "SQL Statement Data"; + } else if (ObjectUtils.equals(element, InvocationSequenceData.class)) { + return "Invocation Sequence Data"; + } else if (ObjectUtils.equals(element, ExceptionSensorData.class)) { + return "Exception Sensor Data"; + } else if (ObjectUtils.equals(element, MemoryInformationData.class)) { + return "Memory Information Data"; + } else if (ObjectUtils.equals(element, CpuInformationData.class)) { + return "CPU Information Data"; + } else if (ObjectUtils.equals(element, ClassLoadingInformationData.class)) { + return "Class Loading Information Data"; + } else if (ObjectUtils.equals(element, ThreadInformationData.class)) { + return "Thread Informartion Data"; + } else if (ObjectUtils.equals(element, SystemInformationData.class)) { + return "System Information Data"; + } else if (ObjectUtils.equals(element, CompilationInformationData.class)) { + return "Compilation Information Data"; + } + return super.getText(element); + } + + /** + * {@inheritDoc} + */ + @Override + public Image getImage(Object element) { + if (ObjectUtils.equals(element, TimerData.class)) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_TIMER); + } else if (ObjectUtils.equals(element, HttpTimerData.class)) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_HTTP); + } else if (ObjectUtils.equals(element, SqlStatementData.class)) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_DATABASE); + } else if (ObjectUtils.equals(element, InvocationSequenceData.class)) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_INVOCATION); + } else if (ObjectUtils.equals(element, ExceptionSensorData.class)) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_EXCEPTION_SENSOR); + } else if (ObjectUtils.equals(element, MemoryInformationData.class)) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_MEMORY_OVERVIEW); + } else if (ObjectUtils.equals(element, CpuInformationData.class)) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_CPU_OVERVIEW); + } else if (ObjectUtils.equals(element, ClassLoadingInformationData.class)) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_CLASS_OVERVIEW); + } else if (ObjectUtils.equals(element, ThreadInformationData.class)) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_THREADS_OVERVIEW); + } else if (ObjectUtils.equals(element, SystemInformationData.class)) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_SYSTEM_OVERVIEW); + } else if (ObjectUtils.equals(element, CompilationInformationData.class)) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_COMPILATION_OVERVIEW); + } + return super.getImage(element); + } + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/DefineNewStorageWizzardPage.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/DefineNewStorageWizzardPage.java new file mode 100644 index 000000000..4979e6775 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/DefineNewStorageWizzardPage.java @@ -0,0 +1,221 @@ +package info.novatec.inspectit.rcp.wizard.page; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.storage.StorageData; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Text; + +/** + * Define storage page. + * + * @author Ivan Senic + * + */ +public class DefineNewStorageWizzardPage extends WizardPage { + + /** + * Default message. + */ + private static final String DEFAULT_MESSAGE = "Enter storage data and select the repository where it will be created"; + + /** + * List of CMR repositories. + */ + private List cmrRepositories; + + /** + * Combo box for repositories. + */ + private Combo cmrRepositoryCombo; + + /** + * Box for storage name. + */ + private Text nameBox; + + /** + * Box for storage description. + */ + private Text descriptionBox; + + /** + * Button for choosing if storage should be auto finalized. + */ + private Button autoFinalize; + + /** + * {@link CmrRepositoryDefinition} that should be initially selected. + */ + private CmrRepositoryDefinition proposedCmrRepositoryDefinition; + + /** + * If auto-finalize button should be selected. + */ + private boolean autoFinalizeSelected; + + /** + * Default constructor. + */ + public DefineNewStorageWizzardPage() { + super("Define New Storage"); + setTitle("Define New Storage"); + setDescription(DEFAULT_MESSAGE); + cmrRepositories = new ArrayList(); + cmrRepositories.addAll(InspectIT.getDefault().getCmrRepositoryManager().getCmrRepositoryDefinitions()); + } + + /** + * This constructor will set provided {@link CmrRepositoryDefinition} as the initially selected + * repository to create storage to. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to create storage on. + */ + public DefineNewStorageWizzardPage(CmrRepositoryDefinition cmrRepositoryDefinition) { + this(cmrRepositoryDefinition, true); + } + + /** + * This constructor will set provided {@link CmrRepositoryDefinition} as the initially selected + * repository to create storage to. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition} to create storage on. + * @param autoFinalizeSecelected + * If auto-finalize button should be selected. + */ + public DefineNewStorageWizzardPage(CmrRepositoryDefinition cmrRepositoryDefinition, boolean autoFinalizeSecelected) { + this(); + this.proposedCmrRepositoryDefinition = cmrRepositoryDefinition; + this.autoFinalizeSelected = autoFinalizeSecelected; + } + + /** + * {@inheritDoc} + */ + @Override + public void createControl(Composite parent) { + Composite main = new Composite(parent, SWT.NONE); + main.setLayout(new GridLayout(2, false)); + + Label cmrSelectLabel = new Label(main, SWT.LEFT); + cmrSelectLabel.setText("Repository:"); + cmrRepositoryCombo = new Combo(main, SWT.READ_ONLY); + cmrRepositoryCombo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + int i = 0; + int index = -1; + for (CmrRepositoryDefinition cmrRepositoryDefinition : cmrRepositories) { + cmrRepositoryCombo.add(cmrRepositoryDefinition.getName() + " (" + cmrRepositoryDefinition.getIp() + ":" + cmrRepositoryDefinition.getPort() + ")"); + if (cmrRepositoryDefinition.equals(proposedCmrRepositoryDefinition)) { + index = i; + } + i++; + } + if (index != -1) { + cmrRepositoryCombo.select(index); + cmrRepositoryCombo.setEnabled(false); + } + + Label nameLabel = new Label(main, SWT.LEFT); + nameLabel.setText("Storage name:"); + nameBox = new Text(main, SWT.BORDER); + nameBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + Label descLabel = new Label(main, SWT.LEFT); + descLabel.setText("Description:"); + descLabel.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + descriptionBox = new Text(main, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.WRAP); + descriptionBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + new Label(main, SWT.LEFT); + autoFinalize = new Button(main, SWT.CHECK); + autoFinalize.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + autoFinalize.setText("Auto-finalize storage"); + autoFinalize.setToolTipText("If selected the storage will be automatically finalized after the action completes"); + autoFinalize.setSelection(autoFinalizeSelected); + + Listener listener = new Listener() { + @Override + public void handleEvent(Event event) { + setPageComplete(isPageComplete()); + if (event.widget.equals(cmrRepositoryCombo) && cmrRepositoryCombo.getSelectionIndex() == -1) { + setMessage("Repository must be selected", IMessageProvider.ERROR); + } else if (event.widget.equals(cmrRepositoryCombo) && getSelectedRepository().getOnlineStatus() == OnlineStatus.OFFLINE) { + setMessage("Selected repository is currently unavailable", IMessageProvider.ERROR); + } else if (event.widget.equals(nameBox) && nameBox.getText().isEmpty()) { + setMessage("Storage name can not be empty", IMessageProvider.ERROR); + } else { + setMessage(DEFAULT_MESSAGE); + } + } + }; + + cmrRepositoryCombo.addListener(SWT.Selection, listener); + nameBox.addListener(SWT.Modify, listener); + + setControl(main); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPageComplete() { + if (cmrRepositoryCombo.getSelectionIndex() == -1) { + return false; + } else if (getSelectedRepository().getOnlineStatus() == OnlineStatus.OFFLINE) { + return false; + } + if (nameBox.getText().isEmpty()) { + return false; + } + return true; + } + + /** + * @return Returns the selected repository or null if noting is selected. + */ + public CmrRepositoryDefinition getSelectedRepository() { + if (cmrRepositoryCombo.getSelectionIndex() != -1) { + return cmrRepositories.get(cmrRepositoryCombo.getSelectionIndex()); + } + return null; + } + + /** + * @return {@link StorageData} that was defined. + */ + public StorageData getStorageData() { + StorageData storageData = new StorageData(); + storageData.setName(nameBox.getText().trim()); + storageData.setDescription(descriptionBox.getText().trim()); + return storageData; + } + + /** + * Returns if auto-finalize options is selected. + * + * @return Returns if auto-finalize options is selected. + */ + public boolean isAutoFinalize() { + return autoFinalize.getSelection(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/DefineTimelineWizardPage.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/DefineTimelineWizardPage.java new file mode 100644 index 000000000..265819a99 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/DefineTimelineWizardPage.java @@ -0,0 +1,411 @@ +package info.novatec.inspectit.rcp.wizard.page; + +import info.novatec.inspectit.storage.processor.AbstractDataProcessor; +import info.novatec.inspectit.storage.processor.impl.TimeFrameDataProcessor; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.nebula.widgets.cdatetime.CDT; +import org.eclipse.nebula.widgets.cdatetime.CDateTime; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Text; + +/** + * Wizard page where user can limit the time frame for the storage write/record. The time frame can + * be in past or future, depending on the {@link #timeStyle} value. + * + * @author Ivan Senic + * + */ +public class DefineTimelineWizardPage extends WizardPage { + + /** + * Time style value for the selecting future time. + */ + public static final int FUTURE = 1; + + /** + * Time style value for the selecting past time. + */ + public static final int PAST = 2; + + /** + * Style that enables the selection of both dates. + */ + public static final int BOTH_DATES = 4; + + /** + * Map of the time characters associated to the multiplier of a second. + */ + private static final Map PERIOD_MULTIPLIERS_MAP = new HashMap(4); + + static { + PERIOD_MULTIPLIERS_MAP.put('m', 1); + PERIOD_MULTIPLIERS_MAP.put('h', 60); + PERIOD_MULTIPLIERS_MAP.put('d', 60 * 24); + PERIOD_MULTIPLIERS_MAP.put('w', 60 * 24 * 7); + } + + /** + * Time style. The style is defining if the page will display the future selection or past time. + * + * @see DefineTimelineWizardPage#FUTURE + * @see DefineTimelineWizardPage#PAST + */ + private int timeStyle; + + /** button for use time frame dividing. */ + private Button useTimeframe; + /** button to the define the period. */ + private Button definePeriod; + /** button to define the date. */ + private Button defineDate; + /** the primary date. */ + private CDateTime cdtPrimary; + /** the secondary date. */ + private CDateTime cdtSecondary; + /** the period. */ + private Text periodAmount; + /** the group. */ + private Group group; + /** the main composite. */ + private Composite main; + /** the composite holding the period. */ + private Composite periodComposite; + /** the composite holding the dates. */ + private Composite cdtComposite; + + /** + * Listener for the page completeness. + */ + private Listener pageCompleteListener = new Listener() { + @Override + public void handleEvent(Event event) { + setPageComplete(isPageComplete()); + } + }; + + /** + * Default message of view. + */ + private String defaultMessage; + + /** + * Default constructor. + * + * @param pageName + * Page name. + * @param defaultMessage + * Default message displayed to the user on the page. + * @param timeStyle + * Time style. The style is defining if the page will display the future selection or + * past time. + * @see DefineTimelineWizardPage#FUTURE + * @see DefineTimelineWizardPage#PAST + */ + public DefineTimelineWizardPage(String pageName, String defaultMessage, int timeStyle) { + super(pageName); + setTitle(pageName); + setMessage(defaultMessage); + this.timeStyle = timeStyle; + this.defaultMessage = defaultMessage; + } + + /** + * {@inheritDoc} + */ + @Override + public void createControl(Composite parent) { + main = new Composite(parent, SWT.NONE); + main.setLayout(new GridLayout(1, false)); + + useTimeframe = new Button(main, SWT.CHECK); + useTimeframe.setText("Use timeframe limiting"); + useTimeframe.setSelection(false); + + group = new Group(main, SWT.NONE); + group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + group.setLayout(new GridLayout(1, false)); + + definePeriod = new Button(group, SWT.RADIO); + definePeriod.setText("Enter wanted time period"); + definePeriod.setSelection(true); + + defineDate = new Button(group, SWT.RADIO); + defineDate.setText("Selected exact date"); + if ((timeStyle & BOTH_DATES) != 0) { + defineDate.setText(defineDate.getText() + "s"); + } + defineDate.setSelection(false); + + useTimeframe.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + if (useTimeframe.getSelection()) { + enableControlAndChildren(group, true); + } else { + enableControlAndChildren(group, false); + } + } + }); + + SelectionAdapter changeWayListener = new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + updatePage(); + } + }; + + defineDate.addSelectionListener(changeWayListener); + definePeriod.addSelectionListener(changeWayListener); + + useTimeframe.addListener(SWT.Selection, pageCompleteListener); + defineDate.addListener(SWT.Selection, pageCompleteListener); + definePeriod.addListener(SWT.Selection, pageCompleteListener); + + updatePage(); + + enableControlAndChildren(group, false); + + setControl(main); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPageComplete() { + if (useTimeframe.getSelection()) { + if (definePeriod.getSelection()) { + Date date = getDateFromPeriodComposite(); + if (null == date) { + setMessage("Time period has to be entered correctly", ERROR); + return false; + } + } else { + if (null == this.getFromDate()) { + setMessage("Please enter starting date & time", ERROR); + return false; + } else if (null == this.getToDate()) { + setMessage("Please enter ending date & time", ERROR); + return false; + } else if (this.getFromDate().after(this.getToDate())) { + if ((timeStyle & BOTH_DATES) != 0) { + setMessage("Start date must be before end date", ERROR); + } else { + setMessage("Entered date must be a future date", ERROR); + } + return false; + } + } + } + setMessage(defaultMessage); + return true; + } + + /** + * @return If the time frame is used. + */ + public boolean isTimerframeUsed() { + return useTimeframe.getSelection(); + } + + /** + * Returns the properly initialized {@link TimeFrameDataProcessor}. + * + * @param chainedProcessors + * Processors that need to be chained to {@link TimeFrameDataProcessor}. + * @return {@link TimeFrameDataProcessor} + * @see {AbstractChainedDataProcessor} + */ + public TimeFrameDataProcessor getTimeFrameDataProcessor(Collection chainedProcessors) { + List normalProcessors = new ArrayList(chainedProcessors); + Date fromDate = getFromDate(); + Date toDate = getToDate(); + TimeFrameDataProcessor timeFrameDataProcessor = new TimeFrameDataProcessor(fromDate, toDate, normalProcessors); + return timeFrameDataProcessor; + } + + /** + * @return Returns from date. Note that if time style is future, here the current date will be + * returned. + */ + public Date getFromDate() { + if ((timeStyle & FUTURE) != 0) { + if ((timeStyle & BOTH_DATES) != 0) { + return (null != cdtPrimary) ? cdtPrimary.getSelection() : null; // NOPMD + } else { + return new Date(); + } + } else { + if (definePeriod.getSelection()) { + return getDateFromPeriodComposite(); + } else { + return (null != cdtPrimary) ? cdtPrimary.getSelection() : null; // NOPMD + } + } + } + + /** + * @return Returns from date. Note that if time style is past, here the current date will be + * returned. + */ + public Date getToDate() { + if ((timeStyle & FUTURE) != 0) { + if (definePeriod.getSelection()) { + return getDateFromPeriodComposite(); + } else if ((timeStyle & BOTH_DATES) != 0) { + return (null != cdtSecondary) ? cdtSecondary.getSelection() : null; // NOPMD + } else { + return (null != cdtPrimary) ? cdtPrimary.getSelection() : null; // NOPMD + } + } else { + if ((timeStyle & BOTH_DATES) != 0) { + return (null != cdtSecondary) ? cdtSecondary.getSelection() : null; // NOPMD + } else { + return new Date(); + } + } + } + + /** + * Enables or disables composite and all its children. + * + * @param composite + * Composite to enable. + * @param enabled + * True for enabling, false for disabling. + */ + private void enableControlAndChildren(Composite composite, boolean enabled) { + composite.setEnabled(enabled); + for (Control child : composite.getChildren()) { + child.setEnabled(enabled); + if (child instanceof Composite) { + enableControlAndChildren((Composite) child, enabled); + } + } + } + + /** + * Updates the widgets on the page based on the selection. + */ + private void updatePage() { + if (null != cdtComposite && !cdtComposite.isDisposed()) { + cdtComposite.dispose(); + } + if (null != periodComposite && !periodComposite.isDisposed()) { + periodComposite.dispose(); + } + + if (definePeriod.getSelection()) { + periodComposite = new Composite(group, SWT.NONE); + periodComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + periodComposite.setLayout(new GridLayout(3, false)); + + Label text = new Label(periodComposite, SWT.NONE); + + periodAmount = new Text(periodComposite, SWT.SINGLE | SWT.BORDER); + periodAmount.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + periodAmount.addListener(SWT.Modify, pageCompleteListener); + periodAmount.setFocus(); + + new Label(periodComposite, SWT.NONE).setText("(ex. 2w 4d 12h 30m)"); + + if ((timeStyle & FUTURE) != 0) { + text.setText("Next:"); + } else { + text.setText("Previous:"); + } + } else { + cdtComposite = new Composite(group, SWT.NONE); + cdtComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + cdtComposite.setLayout(new GridLayout(1, true)); + + cdtPrimary = new CDateTime(cdtComposite, CDT.BORDER | CDT.DROP_DOWN | CDT.TAB_FIELDS); + cdtPrimary.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + cdtPrimary.addListener(SWT.Modify, pageCompleteListener); + + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + if ((timeStyle & FUTURE) != 0 && (timeStyle & BOTH_DATES) == 0) { + cdtPrimary.setPattern("'Till\t\t' EEEE, MMMM d YYYY '@' h:mm a"); + calendar.add(Calendar.HOUR, 1); + } else { + cdtPrimary.setPattern("'From\t' EEEE, MMMM d YYYY '@' h:mm a"); + } + cdtPrimary.setSelection(calendar.getTime()); + + if ((timeStyle & BOTH_DATES) != 0) { + cdtSecondary = new CDateTime(cdtComposite, CDT.BORDER | CDT.DROP_DOWN | CDT.TAB_FIELDS); + cdtSecondary.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + calendar.add(Calendar.HOUR, 1); + cdtSecondary.setSelection(calendar.getTime()); + cdtSecondary.addListener(SWT.Modify, pageCompleteListener); + cdtSecondary.setPattern("'Till\t\t' EEEE, MMMM d YYYY '@' h:mm a"); + } + } + + group.layout(); + main.layout(); + } + + /** + * @return Gets date from the date composite. + */ + private Date getDateFromPeriodComposite() { + try { + Calendar cal = Calendar.getInstance(); + String periodString = periodAmount.getText().trim(); + StringTokenizer tokenizer = new StringTokenizer(periodString, " "); + int perAmountInMinutes = 0; + while (tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken(); + Character timeChar = token.charAt(token.length() - 1); + Integer timeMultiplayer = PERIOD_MULTIPLIERS_MAP.get(timeChar); + if (null != timeMultiplayer) { + try { + int value = Integer.parseInt(token.substring(0, token.length() - 1)); + if (value > 0) { + perAmountInMinutes += value * timeMultiplayer.intValue(); + } + } catch (Exception e) { + continue; + } + } + } + + if (perAmountInMinutes > 0) { + if ((timeStyle & FUTURE) != 0) { + cal.add(Calendar.MINUTE, perAmountInMinutes); + } else { + cal.add(Calendar.MINUTE, -(perAmountInMinutes)); + } + return cal.getTime(); + } else { + return null; + } + } catch (Exception e) { + return null; + } + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/ExportStorageWizardPage.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/ExportStorageWizardPage.java new file mode 100644 index 000000000..59e106996 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/ExportStorageWizardPage.java @@ -0,0 +1,133 @@ +package info.novatec.inspectit.rcp.wizard.page; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.composite.StorageInfoComposite; +import info.novatec.inspectit.storage.IStorageData; +import info.novatec.inspectit.storage.LocalStorageData; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.StorageFileType; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** + * Page for exporting storage. + * + * @author Ivan Senic + * + */ +public class ExportStorageWizardPage extends WizardPage { + + /** + * Default message for wizard page. + */ + private static final String DEFAULT_MESSAGE = "Select where to export storage"; + + /** + * Storage to export. + */ + private IStorageData storageData; + + /** + * Text box where selected file will be displayed. + */ + private Text fileText; + + /** + * Default constructor. + * + * @param storageData + * Storage to export. + */ + public ExportStorageWizardPage(IStorageData storageData) { + super("Export Storage"); + this.setTitle("Export Storage"); + this.setMessage(DEFAULT_MESSAGE); + this.storageData = storageData; + + } + + /** + * {@inheritDoc} + */ + @Override + public void createControl(Composite parent) { + Composite main = new Composite(parent, SWT.NONE); + main.setLayout(new GridLayout(3, false)); + + new Label(main, SWT.NONE).setText("File:"); + + fileText = new Text(main, SWT.READ_ONLY | SWT.BORDER); + fileText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + Button select = new Button(main, SWT.PUSH); + select.setText("Select"); + select.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + FileDialog fileDialog = new FileDialog(getShell(), SWT.SAVE); + fileDialog.setOverwrite(true); + fileDialog.setText("Select File for Export"); + fileDialog.setFilterExtensions(new String[] { "*" + StorageFileType.ZIP_STORAGE_FILE.getExtension() }); + fileDialog.setFileName(storageData.getName()); + String file = fileDialog.open(); + if (null != file) { + fileText.setText(file); + } + setPageComplete(isPageComplete()); + if (fileText.getText().isEmpty()) { + setMessage("No file selected", ERROR); + } else { + setMessage(DEFAULT_MESSAGE); + } + } + }); + select.forceFocus(); + + StorageInfoComposite storageInfoComposite = new StorageInfoComposite(main, SWT.NONE, true, storageData); + storageInfoComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1)); + + LocalStorageData localStorageData = null; + if (storageData instanceof LocalStorageData) { + localStorageData = (LocalStorageData) storageData; + } else if (storageData instanceof StorageData) { + localStorageData = InspectIT.getDefault().getInspectITStorageManager().getLocalDataForStorage((StorageData) storageData); + } + boolean notDownloaded = (null == localStorageData || !localStorageData.isFullyDownloaded()); + if (notDownloaded) { + Composite infoComposite = new Composite(main, SWT.NONE); + infoComposite.setLayout(new GridLayout(2, false)); + infoComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1)); + new Label(infoComposite, SWT.NONE).setImage(JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_INFO)); + new Label(infoComposite, SWT.WRAP).setText("The not downloaded storage will have to be downloaded prior to export operation."); + } + + setControl(main); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPageComplete() { + return !fileText.getText().isEmpty(); + } + + /** + * @return Returns the selected file name. + */ + public String getFileName() { + return fileText.getText(); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/ImportStorageInfoPage.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/ImportStorageInfoPage.java new file mode 100644 index 000000000..36b661847 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/ImportStorageInfoPage.java @@ -0,0 +1,254 @@ +package info.novatec.inspectit.rcp.wizard.page; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.composite.StorageInfoComposite; +import info.novatec.inspectit.rcp.formatter.NumberFormatter; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.rcp.storage.InspectITStorageManager; +import info.novatec.inspectit.storage.IStorageData; +import info.novatec.inspectit.storage.LocalStorageData; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.Collections; +import java.util.List; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; + +/** + * The page that displays the data about storage to be loaded after the file for import has been + * selected. + * + * @author Ivan Senic + * + */ +public class ImportStorageInfoPage extends WizardPage { + + /** + * Default wizard page message. + */ + private static final String DEFAULT_MESSAGE = "Preview storage data before import"; + + /** + * Should import be local. + */ + private boolean importLocally; + + /** + * File name of the storage zip file. + */ + private String fileName; + + /** + * {@link CmrRepositoryDefinition} if import is remote. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * Boolean that denotes if import is possible. + */ + private boolean canImport = false; + + /** + * Label to display file name. + */ + private Label file; + + /** + * Label to display where to import. + */ + private Label importTo; + + /** + * Main composite. + */ + private Composite main; + + /** + * {@link StorageInfoComposite}. + */ + private StorageInfoComposite storageInfoComposite; + + /** + * Default constructor. + */ + public ImportStorageInfoPage() { + super("Import Storage"); + this.setTitle("Import Storage"); + this.setMessage(DEFAULT_MESSAGE); + } + + /** + * {@inheritDoc} + */ + @Override + public void createControl(Composite parent) { + main = new Composite(parent, SWT.NONE); + main.setLayout(new GridLayout(2, false)); + + new Label(main, SWT.NONE).setText("Selected file:"); + file = new Label(main, SWT.WRAP); + + new Label(main, SWT.NONE).setText("Import to:"); + importTo = new Label(main, SWT.WRAP); + + storageInfoComposite = new StorageInfoComposite(main, SWT.NONE, false); + storageInfoComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); + + setControl(main); + } + + /** + * Updates the page content by loading the storage information from file. + */ + public void update() { + // first reset all + reset(); + + // then load info in job + Job updateImportStoragePage = new Job("Update Import Storage Info Page") { + @Override + protected IStatus run(IProgressMonitor monitor) { + boolean callServices = !importLocally && cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE; + final List existingStorages = callServices ? cmrRepositoryDefinition.getStorageService().getExistingStorages() : Collections. emptyList(); + final long spaceLeftOnCmr = callServices ? cmrRepositoryDefinition.getCmrManagementService().getCmrStatusData().getStorageDataSpaceLeft() : 0; + + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + if (null == fileName) { + canImport = false; + } else { + file.setText(fileName); + if (importLocally) { + importTo.setText("Import locally"); + } else { + importTo.setText(cmrRepositoryDefinition.getName()); + } + InspectITStorageManager storageManager = InspectIT.getDefault().getInspectITStorageManager(); + IStorageData storageData = storageManager.getStorageDataFromZip(fileName); + if (null != storageData) { + storageInfoComposite.displayStorageData(storageData); + if (importLocally) { + boolean notImportedYet = true; + for (LocalStorageData localStorageData : storageManager.getDownloadedStorages()) { + if (ObjectUtils.equals(localStorageData.getId(), storageData.getId())) { + notImportedYet = false; + break; + } + } + if (notImportedYet) { + canImport = true; + setMessage(DEFAULT_MESSAGE); + } else { + canImport = false; + setMessage("Selected storage to import is already available locally", ERROR); + } + } else if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + boolean notImportedYet = true; + for (StorageData storageDataOnRepository : existingStorages) { + if (ObjectUtils.equals(storageDataOnRepository.getId(), storageData.getId())) { + notImportedYet = false; + break; + } + } + + boolean enoughSpace = spaceLeftOnCmr > storageData.getDiskSize(); + + if (notImportedYet && enoughSpace) { + canImport = true; + + // check the CMR version + String cmrVersion = cmrRepositoryDefinition.getVersion(); + if (null == storageData.getCmrVersion()) { + setMessage("Selected storage does not define CMR version. The storage might be unstable on the CMR version " + cmrVersion + ".", WARNING); + } else if (!ObjectUtils.equals(storageData.getCmrVersion(), cmrVersion)) { + setMessage("Selected storage has different CMR version than the current CMR version " + cmrVersion + ". The storage might be unstable.", WARNING); + } else { + setMessage(DEFAULT_MESSAGE); + } + } else if (!notImportedYet) { + canImport = false; + setMessage("Selected storage to import is already available on selected CMR", ERROR); + } else if (!enoughSpace) { + canImport = false; + setMessage("Insufficient storage space of the selected repository (" + NumberFormatter.humanReadableByteCount(spaceLeftOnCmr) + " left)", ERROR); + } + } else { + canImport = false; + setMessage("Can not import storage to selected CMR because the CMR is offline", ERROR); + } + } else { + storageInfoComposite.showDataUnavailable(); + canImport = false; + setMessage("Provided file is not valid inspectIT compressed storage file", ERROR); + } + + main.layout(); + main.update(); + } + setPageComplete(isPageComplete()); + } + }); + + return Status.OK_STATUS; + } + }; + updateImportStoragePage.schedule(); + } + + /** + * Resets the page. + */ + public void reset() { + canImport = false; + setPageComplete(false); + } + + @Override + public boolean isPageComplete() { + return canImport; + } + + /** + * Sets {@link #importLocally}. + * + * @param importLocally + * New value for {@link #importLocally} + */ + public void setImportLocally(boolean importLocally) { + this.importLocally = importLocally; + } + + /** + * Sets {@link #fileName}. + * + * @param fileName + * New value for {@link #fileName} + */ + public void setFileName(String fileName) { + this.fileName = fileName; + } + + /** + * Sets {@link #cmrRepositoryDefinition}. + * + * @param cmrRepositoryDefinition + * New value for {@link #cmrRepositoryDefinition} + */ + public void setCmrRepositoryDefinition(CmrRepositoryDefinition cmrRepositoryDefinition) { + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/ImportStorageSelectPage.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/ImportStorageSelectPage.java new file mode 100644 index 000000000..fcbf838f8 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/ImportStorageSelectPage.java @@ -0,0 +1,199 @@ +package info.novatec.inspectit.rcp.wizard.page; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.storage.StorageFileType; + +import java.util.List; + +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Text; + +/** + * Wizard page for selection of the storage to import. + * + * @author Ivan Senic + * + */ +public class ImportStorageSelectPage extends WizardPage { + + /** + * Default wizard page message. + */ + private static final String DEFAULT_MESSAGE = "Select a file to import and a destination"; + + /** + * List of available CMR repositories. + */ + private List cmrRepositoryList; + + /** + * Button for selecting if storage should be imported locally. + */ + private Button locallyButton; + + /** + * Combo for selecting the CMR. + */ + private Combo cmrCombo; + + /** + * Text box where the file name will be displayed. + */ + private Text fileText; + + /** + * Default constructor. + */ + public ImportStorageSelectPage() { + super("Import Storage"); + this.setTitle("Import Storage"); + this.setMessage(DEFAULT_MESSAGE); + cmrRepositoryList = InspectIT.getDefault().getCmrRepositoryManager().getCmrRepositoryDefinitions(); + } + + /** + * {@inheritDoc} + */ + @Override + public void createControl(Composite parent) { + Composite main = new Composite(parent, SWT.NONE); + main.setLayout(new GridLayout(3, false)); + + new Label(main, SWT.NONE).setText("File:"); + + fileText = new Text(main, SWT.READ_ONLY | SWT.BORDER); + fileText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + Button select = new Button(main, SWT.PUSH); + select.setText("Select"); + select.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + FileDialog fileDialog = new FileDialog(getShell(), SWT.OPEN); + fileDialog.setText("Select File to Import"); + fileDialog.setFilterExtensions(new String[] { "*" + StorageFileType.ZIP_STORAGE_FILE.getExtension() }); + String file = fileDialog.open(); + if (null != file) { + fileText.setText(file); + } + } + }); + + Label lbl = new Label(main, SWT.NONE); + lbl.setText("Import to:"); + lbl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); + + locallyButton = new Button(main, SWT.RADIO); + locallyButton.setSelection(true); + locallyButton.setText("Local machine"); + locallyButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); + locallyButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + cmrCombo.setEnabled(!locallyButton.getSelection()); + } + }); + + new Label(main, SWT.NONE); + final Button cmrButton = new Button(main, SWT.RADIO); + cmrButton.setText("CMR Repository"); + cmrButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); + + new Label(main, SWT.NONE); + cmrCombo = new Combo(main, SWT.DROP_DOWN | SWT.READ_ONLY); + for (CmrRepositoryDefinition cmrRepositoryDefinition : cmrRepositoryList) { + cmrCombo.add(cmrRepositoryDefinition.getName()); + } + cmrCombo.setEnabled(false); + cmrCombo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); + + Listener pageCompleteListener = new Listener() { + @Override + public void handleEvent(Event event) { + setPageComplete(isPageComplete()); + if (fileText.getText().isEmpty()) { + setMessage("No file selected", ERROR); + return; + } else if (!locallyButton.getSelection()) { + if (cmrCombo.getSelectionIndex() == -1) { + setMessage("No CMR Repository selected", ERROR); + return; + } else { + CmrRepositoryDefinition cmrRepositoryDefinition = cmrRepositoryList.get(cmrCombo.getSelectionIndex()); + if (cmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.OFFLINE) { + setMessage("Selected CMR Repository is offline.", ERROR); + return; + } + } + } + setMessage(DEFAULT_MESSAGE); + } + }; + + select.addListener(SWT.Selection, pageCompleteListener); + locallyButton.addListener(SWT.Selection, pageCompleteListener); + cmrCombo.addListener(SWT.Selection, pageCompleteListener); + + select.forceFocus(); + + setControl(main); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPageComplete() { + if (fileText.getText().isEmpty()) { + return false; + } + if (!locallyButton.getSelection()) { + if (cmrCombo.getSelectionIndex() == -1) { + return false; + } + CmrRepositoryDefinition cmrRepositoryDefinition = cmrRepositoryList.get(cmrCombo.getSelectionIndex()); + if (cmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.OFFLINE) { + return false; + } + } + return true; + } + + /** + * @return Returns the selected file name. + */ + public String getFileName() { + return fileText.getText(); + } + + /** + * @return If storage should be imported locally. + */ + public boolean isImportLocally() { + return locallyButton.getSelection(); + } + + /** + * @return Returns {@link CmrRepositoryDefinition} if any is selected for import. + */ + public CmrRepositoryDefinition getCmrRepositoryDefinition() { + if (-1 != cmrCombo.getSelectionIndex()) { + return cmrRepositoryList.get(cmrCombo.getSelectionIndex()); + } + return null; + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/ManageLabelWizardPage.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/ManageLabelWizardPage.java new file mode 100644 index 000000000..2aa56539a --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/ManageLabelWizardPage.java @@ -0,0 +1,1013 @@ +package info.novatec.inspectit.rcp.wizard.page; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.editor.viewers.StyledCellIndexLabelProvider; +import info.novatec.inspectit.rcp.formatter.ImageFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.storage.label.composite.AbstractStorageLabelComposite; +import info.novatec.inspectit.rcp.storage.label.composite.impl.BooleanStorageLabelComposite; +import info.novatec.inspectit.rcp.storage.label.composite.impl.DateStorageLabelComposite; +import info.novatec.inspectit.rcp.storage.label.composite.impl.NumberStorageLabelComposite; +import info.novatec.inspectit.rcp.storage.label.composite.impl.StringStorageLabelComposite; +import info.novatec.inspectit.storage.label.AbstractStorageLabel; +import info.novatec.inspectit.storage.label.management.AbstractLabelManagementAction; +import info.novatec.inspectit.storage.label.management.impl.AddLabelManagementAction; +import info.novatec.inspectit.storage.label.management.impl.RemoveLabelManagementAction; +import info.novatec.inspectit.storage.label.type.AbstractCustomStorageLabelType; +import info.novatec.inspectit.storage.label.type.AbstractStorageLabelType; +import info.novatec.inspectit.storage.label.type.impl.CustomBooleanLabelType; +import info.novatec.inspectit.storage.label.type.impl.CustomDateLabelType; +import info.novatec.inspectit.storage.label.type.impl.CustomNumberLabelType; +import info.novatec.inspectit.storage.label.type.impl.CustomStringLabelType; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.forms.widgets.FormText; + +/** + * Manage label page. + * + * @author Ivan Senic + * + */ +public class ManageLabelWizardPage extends WizardPage { + + /** + * Default message. + */ + private static final String DEFAULT_MESSAGE = "Add and remove labels that can be used later for labeling storages"; + + /** + * Empty style string. + */ + private static final StyledString EMPTY_STYLED_STRING = new StyledString(); + + /** + * Available remote label list. + */ + private List> labelList = new ArrayList>(); + + /** + * Available remote label type list. + */ + private List> labelTypeList = new ArrayList>(); + + /** + * Table viewer for labels. + */ + private TableViewer labelsTableViewer; + + /** + * Remove label button. + */ + private Button removeLabels; + + /** + * Add label button. + */ + private Button createLabel; + + /** + * {@link TableViewer} for label types. + */ + private TableViewer labelTypeTableViewer; + + /** + * Create label type button. + */ + private Button createLabelType; + + /** + * Remove label types button. + */ + private Button removeLabelType; + + /** + * List of labels in storages. + */ + private Set> labelsInStorages = new HashSet>(); + + /** + * List of actions that need to be executed at the end of wizard. + */ + private List managementActions = new LinkedList(); + + /** + * {@link CmrRepositoryDefinition}. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * Default constructor. + * + * @param cmrRepositoryDefinition + * CMR to manage labels for. + */ + public ManageLabelWizardPage(CmrRepositoryDefinition cmrRepositoryDefinition) { + super("Manage Labels"); + this.setTitle("Manage Labels"); + this.setMessage(DEFAULT_MESSAGE); + if (null != cmrRepositoryDefinition) { + this.setMessage("Label management for repository '" + cmrRepositoryDefinition.getName() + "' (" + cmrRepositoryDefinition.getIp() + ":" + cmrRepositoryDefinition.getPort() + ")"); + } + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public void createControl(Composite parent) { + SashForm sashForm = new SashForm(parent, SWT.VERTICAL); + + // label type - upper composite + createLabelTypeTable(sashForm); + // labels - lower composite + createLabelTable(sashForm); + + sashForm.setWeights(new int[] { 1, 1 }); + setControl(sashForm); + } + + /** + * Creates the table for the label types. + * + * @param parent + * Parent composite. + */ + private void createLabelTypeTable(Composite parent) { + Composite upperComposite = new Composite(parent, SWT.NONE); + upperComposite.setLayout(new GridLayout(2, false)); + + Label labelTypeInfo = new Label(upperComposite, SWT.NONE); + labelTypeInfo.setText("Existing label types"); + labelTypeInfo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 2, 1)); + + Table labelTypeTable = new Table(upperComposite, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.VIRTUAL | SWT.FULL_SELECTION); + labelTypeTable.setHeaderVisible(true); + GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 2); + gd.heightHint = 150; + labelTypeTable.setLayoutData(gd); + + TableColumn column = new TableColumn(labelTypeTable, SWT.NONE); + column.setMoveable(false); + column.setResizable(false); + column.setWidth(25); + + column = new TableColumn(labelTypeTable, SWT.NONE); + column.setText("Name"); + column.setMoveable(false); + column.setResizable(true); + column.setWidth(200); + + column = new TableColumn(labelTypeTable, SWT.NONE); + column.setText("Value type"); + column.setMoveable(false); + column.setResizable(true); + column.setWidth(100); + + column = new TableColumn(labelTypeTable, SWT.NONE); + column.setText("One per storage"); + column.setMoveable(false); + column.setResizable(true); + column.setWidth(80); + + labelTypeTableViewer = new TableViewer(labelTypeTable); + labelTypeTableViewer.setContentProvider(new ArrayContentProvider()); + labelTypeTableViewer.setLabelProvider(new StyledCellIndexLabelProvider() { + @Override + protected StyledString getStyledText(Object element, int index) { + if (element instanceof AbstractStorageLabelType) { + AbstractStorageLabelType labelType = (AbstractStorageLabelType) element; + switch (index) { + case 1: + return new StyledString(TextFormatter.getLabelName(labelType)); + case 2: + return new StyledString(TextFormatter.getLabelValueType(labelType)); + case 3: + if (labelType.isOnePerStorage()) { + return new StyledString("Yes"); + } else { + return new StyledString("No"); + } + default: + break; + } + } + return EMPTY_STYLED_STRING; + } + + @Override + protected Image getColumnImage(Object element, int index) { + if (index == 0 && element instanceof AbstractStorageLabelType) { + if (isLabelTypeExistsInStorage((AbstractStorageLabelType) element, labelsInStorages)) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_STORAGE); + } + } else if (index == 1 && element instanceof AbstractStorageLabelType) { + return ImageFormatter.getImageForLabel((AbstractStorageLabelType) element); + } + return null; + } + }); + labelTypeTableViewer.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + manageLabelTypeSlection(); + } + }); + labelTypeTableViewer.setInput(labelTypeList); + + createLabelType = new Button(upperComposite, SWT.PUSH); + createLabelType.setText("Add"); + createLabelType.setToolTipText("Create New Label Type"); + createLabelType.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + createLabelType.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + CreateLabelTypeDialog createDialog = new CreateLabelTypeDialog(getShell()); + createDialog.open(); + if (createDialog.getReturnCode() == Dialog.OK) { + AbstractStorageLabelType createdType = createDialog.getCreatedLabelType(); + AddLabelManagementAction addLabelManagementAction = new AddLabelManagementAction(createdType); + managementActions.add(addLabelManagementAction); + labelTypeList.add(createdType); + labelTypeTableViewer.refresh(); + } + } + }); + + removeLabelType = new Button(upperComposite, SWT.PUSH); + removeLabelType.setText("Remove"); + removeLabelType.setToolTipText("Remove Label Type"); + removeLabelType.setEnabled(false); + removeLabelType.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + removeLabelType.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + AbstractStorageLabelType typeToRemove = (AbstractStorageLabelType) ((StructuredSelection) labelTypeTableViewer.getSelection()).getFirstElement(); + boolean removeFromStorage = false; + if (isLabelTypeExistsInStorage(typeToRemove, labelsInStorages)) { + MessageDialog messageDialog = new MessageDialog(getShell(), "Remove Label Type", null, "Should all labels of selected type be removed also from storages where they are used?", + MessageDialog.QUESTION, new String[] { "Yes", "No" }, 1); + if (messageDialog.open() == 0) { + removeFromStorage = true; + } + } + RemoveLabelManagementAction removeLabelManagementAction = new RemoveLabelManagementAction(typeToRemove, removeFromStorage); + managementActions.add(removeLabelManagementAction); + labelTypeList.remove(typeToRemove); + Iterator> it = labelList.iterator(); + while (it.hasNext()) { + if (ObjectUtils.equals(typeToRemove, it.next().getStorageLabelType())) { + it.remove(); + } + } + labelTypeTableViewer.refresh(); + } + }); + + Job loadDataJob = new Job("Loading Labels Data") { + @Override + protected IStatus run(IProgressMonitor monitor) { + labelsInStorages.addAll(cmrRepositoryDefinition.getStorageService().getAllLabelsInStorages()); + labelTypeList.addAll(cmrRepositoryDefinition.getStorageService().getAllLabelTypes()); + labelList.addAll(cmrRepositoryDefinition.getStorageService().getAllLabels()); + + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + labelsTableViewer.refresh(); + labelTypeTableViewer.refresh(); + } + }); + + return Status.OK_STATUS; + } + }; + loadDataJob.schedule(); + } + + /** + * Creates the table for the labels. + * + * @param parent + * Parent composite. + */ + private void createLabelTable(Composite parent) { + Composite lowerComposite = new Composite(parent, SWT.NONE); + lowerComposite.setLayout(new GridLayout(2, false)); + + Table table = new Table(lowerComposite, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.VIRTUAL | SWT.FULL_SELECTION); + table.setHeaderVisible(true); + GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 2); + gd.heightHint = 150; + table.setLayoutData(gd); + + TableColumn column = new TableColumn(table, SWT.NONE); + column.setMoveable(false); + column.setResizable(false); + column.setWidth(25); + + column = new TableColumn(table, SWT.NONE); + column.setText("Label"); + column.setMoveable(false); + column.setResizable(true); + column.setWidth(200); + + column = new TableColumn(table, SWT.NONE); + column.setText("Value"); + column.setMoveable(false); + column.setResizable(true); + column.setWidth(100); + + labelsTableViewer = new TableViewer(table); + labelsTableViewer.setContentProvider(new ArrayContentProvider()); + labelsTableViewer.setLabelProvider(new StyledCellIndexLabelProvider() { + @Override + protected StyledString getStyledText(Object element, int index) { + if (element instanceof AbstractStorageLabel) { + AbstractStorageLabel label = (AbstractStorageLabel) element; + switch (index) { + case 1: + return new StyledString(TextFormatter.getLabelName(label)); + case 2: + return new StyledString(TextFormatter.getLabelValue(label, false)); + default: + break; + } + } + return EMPTY_STYLED_STRING; + } + + @Override + protected Image getColumnImage(Object element, int index) { + if (index == 0 && element instanceof AbstractStorageLabel) { + if (isLabelExistsInStorage((AbstractStorageLabel) element, labelsInStorages)) { + return InspectIT.getDefault().getImage(InspectITImages.IMG_STORAGE); + } + } + if (index == 1 && element instanceof AbstractStorageLabel) { + return ImageFormatter.getImageForLabel(((AbstractStorageLabel) element).getStorageLabelType()); + } + return null; + } + }); + labelsTableViewer.setComparator(new ViewerComparator() { + @Override + public int compare(Viewer viewer, Object e1, Object e2) { + if (e1 instanceof AbstractStorageLabel && e2 instanceof AbstractStorageLabel) { + return ((AbstractStorageLabel) e1).compareTo((AbstractStorageLabel) e2); + } + return super.compare(viewer, e1, e2); + } + }); + labelsTableViewer.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + if (labelsTableViewer.getSelection().isEmpty()) { + removeLabels.setEnabled(false); + } else { + removeLabels.setEnabled(true); + } + } + }); + + createLabel = new Button(lowerComposite, SWT.PUSH); + createLabel.setText("Add"); + createLabel.setToolTipText("Create New Label"); + createLabel.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + createLabel.setEnabled(false); + createLabel.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + AbstractStorageLabelType suggestedLabelType = (AbstractStorageLabelType) ((StructuredSelection) labelTypeTableViewer.getSelection()).getFirstElement(); + CreateLabelDialog createLabelDialog = new CreateLabelDialog(getShell(), labelTypeList, suggestedLabelType); + createLabelDialog.open(); + if (createLabelDialog.getReturnCode() == Dialog.OK) { + AbstractStorageLabel createdLabel = createLabelDialog.getCreatedLabel(); + List> createdList = new ArrayList>(1); + createdList.add(createdLabel); + AddLabelManagementAction addLabelManagementAction = new AddLabelManagementAction(createdList); + managementActions.add(addLabelManagementAction); + labelList.add(createdLabel); + manageLabelTypeSlection(); + } + } + }); + + removeLabels = new Button(lowerComposite, SWT.PUSH); + removeLabels.setText("Remove"); + removeLabels.setToolTipText("Remove Label(s)"); + removeLabels.setEnabled(false); + removeLabels.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + removeLabels.addSelectionListener(new SelectionAdapter() { + @SuppressWarnings("unchecked") + @Override + public void widgetSelected(SelectionEvent e) { + List> labelsToRemove = ((StructuredSelection) labelsTableViewer.getSelection()).toList(); + boolean inStorage = false; + for (AbstractStorageLabel label : labelsToRemove) { + if (isLabelExistsInStorage(label, labelsInStorages)) { + inStorage = true; + break; + } + } + + boolean removeFromStorage = false; + if (inStorage) { + MessageDialog messageDialog = new MessageDialog(getShell(), "Remove Label(s)", null, "Should all selected labels be removed also from storages where they are used?", + MessageDialog.QUESTION, new String[] { "Yes", "No" }, 1); + if (messageDialog.open() == 0) { + removeFromStorage = true; + labelsInStorages.removeAll(labelsToRemove); + } + } + RemoveLabelManagementAction removeLabelManagementAction = new RemoveLabelManagementAction(labelsToRemove, removeFromStorage); + managementActions.add(removeLabelManagementAction); + labelList.removeAll(labelsToRemove); + manageLabelTypeSlection(); + labelTypeTableViewer.refresh(true); + } + }); + + FormText storageInfo = new FormText(lowerComposite, SWT.NONE); + storageInfo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 2, 1)); + storageInfo.setImage("storage", InspectIT.getDefault().getImage(InspectITImages.IMG_STORAGE)); + storageInfo.setText("

The icon denotes that label type or label is currently used in one or more storages.

", true, false); + } + + /** + * Returns if any storage in collection of storages does contain at least one label that is of + * the given label type. + * + * @param labelType + * Label type to search for. + * @param labelsInStorages + * Storages to check. + * @return True if at least one label of given type exists in one of given storages. + */ + private boolean isLabelTypeExistsInStorage(AbstractStorageLabelType labelType, Set> labelsInStorages) { + for (AbstractStorageLabel label : labelsInStorages) { + if (ObjectUtils.equals(label.getStorageLabelType(), labelType)) { + return true; + } + } + return false; + } + + /** + * Returns if any storage in collection of storages does contain given label. + * + * @param label + * Label to search for. + * @param labelsInStorages + * Storages to check. + * @return True if label exists in one of given storages. + */ + private boolean isLabelExistsInStorage(AbstractStorageLabel label, Set> labelsInStorages) { + for (AbstractStorageLabel labelInStorage : labelsInStorages) { + if (ObjectUtils.equals(label, labelInStorage)) { + return true; + } + } + return false; + } + + /** + * Manages the label type selection. + */ + private void manageLabelTypeSlection() { + if (!labelTypeTableViewer.getSelection().isEmpty()) { + AbstractStorageLabelType labelType = (AbstractStorageLabelType) ((StructuredSelection) labelTypeTableViewer.getSelection()).getFirstElement(); + List> inputForLabelTable = new ArrayList>(); + for (AbstractStorageLabel label : labelList) { + if (ObjectUtils.equals(label.getStorageLabelType(), labelType)) { + inputForLabelTable.add(label); + } + } + if (labelType.isValueReusable()) { + createLabel.setEnabled(true); + labelsTableViewer.getTable().setEnabled(true); + } else { + createLabel.setEnabled(false); + labelsTableViewer.getTable().setEnabled(false); + } + removeLabelType.setEnabled(AbstractCustomStorageLabelType.class.isAssignableFrom(labelType.getClass())); + labelsTableViewer.setInput(inputForLabelTable); + labelsTableViewer.refresh(); + } else { + removeLabelType.setEnabled(false); + createLabel.setEnabled(true); + labelsTableViewer.getTable().setEnabled(true); + labelsTableViewer.setInput(null); + labelsTableViewer.refresh(); + } + } + + /** + * Gets {@link #shouldRefreshStorages}. + * + * @return {@link #shouldRefreshStorages} + */ + public boolean isShouldRefreshStorages() { + return !managementActions.isEmpty(); + } + + /** + * Gets {@link #managementActions}. + * + * @return {@link #managementActions} + */ + public List getManagementActions() { + return managementActions; + } + + /** + * Create label type dialog. + * + * @author Ivan Senic + * + */ + private static class CreateLabelTypeDialog extends TitleAreaDialog { + + /** + * Available type list. + */ + private final AbstractCustomStorageLabelType[] availableTypes = new AbstractCustomStorageLabelType[] { new CustomBooleanLabelType(), new CustomDateLabelType(), + new CustomNumberLabelType(), new CustomStringLabelType() }; + + /** + * Reference to the created label type. + */ + private AbstractStorageLabelType createdLabelType; + + /** + * Keys of the images that can be used in custom labels. + */ + private final String[] imageKeys; + + /** + * Index of the selected image key. + */ + private int selectedImageKeyIndex = -1; + + /** Image buttons. */ + private Button[] imageButtons; + /** Ok Button. */ + private Button okButton; + /** The main window. */ + private Composite main; + /** the name. */ + private Text name; + /** Selection of the value type. */ + private Combo valueTypeSelection; + /** yes button. */ + private Button yesButton; + /** no button. */ + private Button noButton; + + /** + * Default constructor. + * + * @param parentShell + * Shell + */ + public CreateLabelTypeDialog(Shell parentShell) { + super(parentShell); + this.imageKeys = ImageFormatter.LABEL_ICONS; + } + + /** + * {@inheritDoc} + */ + @Override + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + newShell.setText("Create Label Type"); + } + + /** + * {@inheritDoc} + */ + @Override + public void create() { + super.create(); + this.setTitle("Create Label Type"); + this.setMessage("Define properties for the new label type", IMessageProvider.INFORMATION); + } + + /** + * {@inheritDoc} + */ + @Override + protected void createButtonsForButtonBar(Composite parent) { + createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, true); + okButton = createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); + okButton.setEnabled(false); + } + + /** + * {@inheritDoc} + */ + @Override + protected void buttonPressed(int buttonId) { + if (buttonId == IDialogConstants.OK_ID) { + createdLabelType = ensureLabelType(); + } + super.buttonPressed(buttonId); + } + + /** + * {@inheritDoc} + */ + @Override + protected Control createDialogArea(Composite parent) { + main = new Composite(parent, SWT.NONE); + GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true); + gd.minimumWidth = 350; + main.setLayoutData(gd); + main.setLayout(new GridLayout(3, false)); + + new Label(main, SWT.NONE).setText("Name:"); + name = new Text(main, SWT.BORDER); + name.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); + + new Label(main, SWT.NONE).setText("Value type:"); + valueTypeSelection = new Combo(main, SWT.DROP_DOWN | SWT.READ_ONLY); + valueTypeSelection.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); + for (AbstractStorageLabelType labelType : availableTypes) { + valueTypeSelection.add(TextFormatter.getLabelValueType(labelType)); + } + + new Label(main, SWT.NONE).setText("One per storage:"); + yesButton = new Button(main, SWT.RADIO); + yesButton.setText("Yes"); + yesButton.setSelection(true); + + noButton = new Button(main, SWT.RADIO); + noButton.setText("No"); + + Label l = new Label(main, SWT.NONE); + l.setText("Icon:"); + l.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + + final ScrolledComposite scrolledComposite = new ScrolledComposite(main, SWT.V_SCROLL | SWT.BORDER); + gd = new GridData(SWT.FILL, SWT.FILL, false, false, 2, 1); + gd.widthHint = 1; + gd.heightHint = 120; + scrolledComposite.setLayoutData(gd); + + final Composite iconComposite = new Composite(scrolledComposite, SWT.NONE); + iconComposite.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); + + // create buttons for selecting images + SelectionAdapter buttonListener = new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + for (int i = 0; i < imageButtons.length; i++) { + if (Objects.equals(imageButtons[i], e.widget)) { + if (selectedImageKeyIndex == i) { + // de-selection occurred + selectedImageKeyIndex = -1; + } else { + selectedImageKeyIndex = i; + } + } else { + imageButtons[i].setSelection(false); + } + } + } + }; + imageButtons = new Button[imageKeys.length]; + for (int i = 0; i < imageKeys.length; i++) { + Button button = new Button(iconComposite, SWT.TOGGLE); + button.setImage(InspectIT.getDefault().getImage(imageKeys[i])); + button.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); + button.addSelectionListener(buttonListener); + imageButtons[i] = button; + } + + RowLayout layout = new RowLayout(SWT.HORIZONTAL); + layout.wrap = true; + layout.fill = true; + layout.justify = true; + layout.spacing = 7; + iconComposite.setLayout(layout); + scrolledComposite.setContent(iconComposite); + scrolledComposite.setExpandVertical(true); + scrolledComposite.setExpandHorizontal(true); + scrolledComposite.addControlListener(new ControlAdapter() { + public void controlResized(ControlEvent e) { + Rectangle r = scrolledComposite.getClientArea(); + scrolledComposite.setMinSize(iconComposite.computeSize(r.width, SWT.DEFAULT)); + } + }); + + final Listener listener = new Listener() { + @Override + public void handleEvent(Event event) { + okButton.setEnabled(isInputValid()); + } + + }; + name.addListener(SWT.Modify, listener); + valueTypeSelection.addListener(SWT.Selection, listener); + yesButton.addListener(SWT.Selection, listener); + noButton.addListener(SWT.Selection, listener); + + name.forceFocus(); + return main; + } + + /** + * @return Returns if the input of the dialog is valid. + */ + private boolean isInputValid() { + if (name.getText().trim().isEmpty()) { + return false; + } + if (valueTypeSelection.getSelectionIndex() == -1) { + return false; + } + return true; + } + + /** + * Ensures that the label type is correctly created. + * + * @return {@link AbstractStorageLabelType} + */ + private AbstractStorageLabelType ensureLabelType() { + AbstractCustomStorageLabelType labelType = availableTypes[valueTypeSelection.getSelectionIndex()]; + labelType.setName(name.getText().trim()); + labelType.setOnePerStorage(yesButton.getSelection()); + if (selectedImageKeyIndex >= 0 && selectedImageKeyIndex < imageKeys.length) { + labelType.setImageKey(imageKeys[selectedImageKeyIndex]); + } + return labelType; + } + + /** + * Gets {@link #createdLabelType}. + * + * @return {@link #createdLabelType} + */ + public AbstractStorageLabelType getCreatedLabelType() { + return createdLabelType; + } + + } + + /** + * Create label dialog. + * + * @author Ivan Senic + * + */ + private static class CreateLabelDialog extends TitleAreaDialog { + + /** + * List of label types that can be created. + */ + private List> labelTypes; + + /** + * Suggested label type that will be initially selected. + */ + private AbstractStorageLabelType suggestedType; + + /** + * Label type selection Combo. + */ + private Combo typeSelection; + + /** + * Storage label composite. + */ + private AbstractStorageLabelComposite storageLabelComposite; + + /** + * OK Button. + */ + private Button okButton; + + /** + * Main composite. + */ + private Composite main; + + /** + * Created label reference. + */ + private AbstractStorageLabel createdLabel; + + /** + * Default constructor. + * + * @param parentShell + * Shell. + * @param labelTypes + * List of labels that can be created. + * @param suggestedType + * Suggested type that will initially be selected in the combo box. + */ + public CreateLabelDialog(Shell parentShell, List> labelTypes, AbstractStorageLabelType suggestedType) { + super(parentShell); + this.labelTypes = labelTypes; + this.suggestedType = suggestedType; + } + + /** + * {@inheritDoc} + */ + @Override + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + newShell.setText("Create Label"); + } + + /** + * {@inheritDoc} + */ + @Override + public void create() { + super.create(); + this.setTitle("Create Label"); + this.setMessage("Selected wanted label type and define label value", IMessageProvider.INFORMATION); + } + + /** + * {@inheritDoc} + */ + @Override + protected void createButtonsForButtonBar(Composite parent) { + createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, true); + okButton = createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); + okButton.setEnabled(isInputValid()); + } + + /** + * {@inheritDoc} + */ + @Override + protected void buttonPressed(int buttonId) { + if (buttonId == IDialogConstants.OK_ID) { + createdLabel = storageLabelComposite.getStorageLabel(); + } + super.buttonPressed(buttonId); + } + + /** + * {@inheritDoc} + */ + @Override + protected Control createDialogArea(Composite parent) { + main = new Composite(parent, SWT.NONE); + GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true); + gd.minimumWidth = 350; + main.setLayoutData(gd); + main.setLayout(new GridLayout(2, false)); + + new Label(main, SWT.NONE).setText("Label type:"); + typeSelection = new Combo(main, SWT.DROP_DOWN | SWT.READ_ONLY); + int index = -1; + int i = 0; + for (AbstractStorageLabelType labelType : labelTypes) { + typeSelection.add(TextFormatter.getLabelName(labelType)); + if (ObjectUtils.equals(labelType, suggestedType)) { + index = i; + } + i++; + } + typeSelection.select(index); + typeSelection.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + typeSelection.setEnabled(false); + + final Listener listener = new Listener() { + @Override + public void handleEvent(Event event) { + okButton.setEnabled(isInputValid()); + } + }; + typeSelection.addListener(SWT.Selection, listener); + typeSelection.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + updateStorageLabelComposite(); + } + }); + + new Label(main, SWT.NONE).setText("Label value:"); + updateStorageLabelComposite(); + + typeSelection.forceFocus(); + return main; + } + + /** + * + * @return Returns created label. + */ + public AbstractStorageLabel getCreatedLabel() { + return createdLabel; + } + + /** + * Updates the storage label composite. + */ + @SuppressWarnings("unchecked") + private void updateStorageLabelComposite() { + if (null != storageLabelComposite && !storageLabelComposite.isDisposed()) { + storageLabelComposite.dispose(); + } + + AbstractStorageLabelType selectedLabelType = labelTypes.get(typeSelection.getSelectionIndex()); + if (selectedLabelType.getValueClass().equals(Boolean.class)) { + storageLabelComposite = new BooleanStorageLabelComposite(main, SWT.NONE, (AbstractStorageLabelType) selectedLabelType, false); + } else if (selectedLabelType.getValueClass().equals(Date.class)) { + storageLabelComposite = new DateStorageLabelComposite(main, SWT.NONE, (AbstractStorageLabelType) selectedLabelType, false); + } else if (selectedLabelType.getValueClass().equals(Number.class)) { + storageLabelComposite = new NumberStorageLabelComposite(main, SWT.NONE, (AbstractStorageLabelType) selectedLabelType, false); + } else if (selectedLabelType.getValueClass().equals(String.class)) { + storageLabelComposite = new StringStorageLabelComposite(main, SWT.NONE, (AbstractStorageLabelType) selectedLabelType, false); + } + + final Listener listener = new Listener() { + @Override + public void handleEvent(Event event) { + okButton.setEnabled(isInputValid()); + } + }; + storageLabelComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + storageLabelComposite.addListener(listener); + main.layout(); + } + + /** + * + * @return If dialog input is valid. + */ + private boolean isInputValid() { + if (typeSelection.getSelectionIndex() == -1) { + return false; + } + if (null == storageLabelComposite || !storageLabelComposite.isInputValid()) { + return false; + } + return true; + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/NewOrExistsingStorageWizardPage.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/NewOrExistsingStorageWizardPage.java new file mode 100644 index 000000000..330818431 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/NewOrExistsingStorageWizardPage.java @@ -0,0 +1,57 @@ +package info.novatec.inspectit.rcp.wizard.page; + +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; + +/** + * Should a new or existing storage should be used. + * + * @author Ivan Senic + * + */ +public class NewOrExistsingStorageWizardPage extends WizardPage { + + /** + * Use enw storage button. + */ + private Button newStorageButton; + + /** + * Default constructor. + */ + public NewOrExistsingStorageWizardPage() { + super("Select Storage"); + this.setTitle("Select Storage"); + this.setMessage("Should a new storage should be created, or existing one should be used."); + } + + /** + * {@inheritDoc} + */ + @Override + public void createControl(Composite parent) { + Composite main = new Composite(parent, SWT.NONE); + main.setLayout(new GridLayout(1, true)); + + newStorageButton = new Button(main, SWT.RADIO); + newStorageButton.setText("Create new storage"); + newStorageButton.setSelection(true); + + new Button(main, SWT.RADIO).setText("Use existing storage"); + + setControl(main); + } + + /** + * Should new storage be used. + * + * @return Should new storage be used. + */ + public boolean useNewStorage() { + return newStorageButton.getSelection(); + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/PreviewCmrDataWizardPage.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/PreviewCmrDataWizardPage.java new file mode 100644 index 000000000..d18c89575 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/PreviewCmrDataWizardPage.java @@ -0,0 +1,231 @@ +package info.novatec.inspectit.rcp.wizard.page; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.InspectITImages; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.ui.internal.forms.widgets.BusyIndicator; +import org.eclipse.ui.progress.IProgressConstants; + +/** + * The wizard page that displays the CMR info and checks for the connection status. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("restriction") +public class PreviewCmrDataWizardPage extends WizardPage { + + /** + * Name label. + */ + private Label name; + + /** + * IP label. + */ + private Label ip; + + /** + * Description label. + */ + private Label description; + + /** + * Connection test result label. + */ + private Label connectionTest; + + /** + * Busy indicator shown while test is executed. + */ + private BusyIndicator busyIndicator; + + /** + * Main composite. + */ + private Composite main; + + /** + * Job for checking the CMR status. + */ + private Job checkCmrJob; + + /** + * Version label. + */ + private Label version; + + /** + * Default constructor. + */ + public PreviewCmrDataWizardPage() { + super("Preview CMR Data"); + this.setTitle("Preview CMR Data"); + this.setMessage("Preview the entered CMR Repository data and confirm"); + } + + /** + * {@inheritDoc} + */ + @Override + public void createControl(Composite parent) { + main = new Composite(parent, SWT.NONE); + main.setLayout(new GridLayout(3, false)); + + Label nameLabel = new Label(main, SWT.LEFT); + nameLabel.setText("Server name:"); + name = new Label(main, SWT.NONE); + name.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); + + Label ipLabel = new Label(main, SWT.LEFT); + ipLabel.setText("IP Address:"); + ip = new Label(main, SWT.NONE); + ip.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); + ip.setText(CmrRepositoryDefinition.DEFAULT_IP); + + Label descLabel = new Label(main, SWT.LEFT); + descLabel.setText("Description:"); + descLabel.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + description = new Label(main, SWT.WRAP); + description.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); + + new Label(main, SWT.LEFT).setText("Connection test:"); + connectionTest = new Label(main, SWT.NONE); + connectionTest.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + busyIndicator = new BusyIndicator(main, SWT.NONE); + busyIndicator.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + + new Label(main, SWT.LEFT).setText("Version:"); + version = new Label(main, SWT.NONE); + version.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); + + setControl(main); + } + + /** + * Updates the representation with a given {@link CmrRepositoryDefinition}. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + */ + public void update(final CmrRepositoryDefinition cmrRepositoryDefinition) { + name.setText(cmrRepositoryDefinition.getName()); + ip.setText(cmrRepositoryDefinition.getIp() + ":" + cmrRepositoryDefinition.getPort()); + description.setText(cmrRepositoryDefinition.getDescription()); + connectionTest.setText("Checking.."); + busyIndicator.setBusy(true); + version.setText(""); + main.layout(); + checkCmrJob = new CheckCmrJob(cmrRepositoryDefinition); + checkCmrJob.schedule(); + } + + /** + * Cancels the checking if it is active. + */ + public void cancel() { + if (null != checkCmrJob) { + checkCmrJob.cancel(); + } + busyIndicator.setBusy(false); + main.layout(); + } + + /** + * Checking of the CRM job. + * + * @author Ivan Senic + * + */ + private class CheckCmrJob extends Job { + + /** + * Is job canceled. + */ + private boolean isCanceled = false; + + /** + * CMR to check. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * Default constructor. + * + * @param cmrRepositoryDefinition + * CMR to check. + */ + public CheckCmrJob(CmrRepositoryDefinition cmrRepositoryDefinition) { + super("Checking online status.."); + setUser(false); + setProperty(IProgressConstants.ICON_PROPERTY, InspectIT.getDefault().getImageDescriptor(InspectITImages.IMG_SERVER_REFRESH_SMALL)); + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + } + + /** + * {@inheritDoc} + */ + @Override + public IStatus run(IProgressMonitor monitor) { + if (null == cmrRepositoryDefinition) { + return Status.CANCEL_STATUS; + } + boolean testOk = false; + try { + cmrRepositoryDefinition.refreshOnlineStatus(); + testOk = cmrRepositoryDefinition.getOnlineStatus() == OnlineStatus.ONLINE; + } catch (Exception exception) { + testOk = false; + } + + String ver = null; + if (isCanceled) { + return Status.CANCEL_STATUS; + } else if (testOk) { + ver = cmrRepositoryDefinition.getVersion(); + } + + final boolean testOkFinal = testOk; + final String verFinal = ver; + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + if (!main.isDisposed()) { + busyIndicator.setBusy(false); + if (testOkFinal) { + connectionTest.setText("Succeeded"); + version.setText(verFinal); + } else { + connectionTest.setText("Failed"); + version.setText("n/a"); + } + main.layout(); + } + } + }); + return Status.OK_STATUS; + } + + /** + * {@inheritDoc} + */ + protected void canceling() { + isCanceled = true; + }; + + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/SelectAgentsWizardPage.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/SelectAgentsWizardPage.java new file mode 100644 index 000000000..481cfa574 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/SelectAgentsWizardPage.java @@ -0,0 +1,284 @@ +package info.novatec.inspectit.rcp.wizard.page; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.communication.data.cmr.AgentStatusData; +import info.novatec.inspectit.rcp.formatter.ImageFormatter; +import info.novatec.inspectit.rcp.formatter.TextFormatter; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections.CollectionUtils; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; + +/** + * Wizard page for selecting the agents. + * + * @author Ivan Senic + * + */ +public class SelectAgentsWizardPage extends WizardPage { + + /** + * Default wizard message. + */ + private static final String DEFAULT_MESSAGE = "Selected Agent(s)"; + + /** + * List of available agents on the server. + */ + private List agentList; + + /** + * Agent selection table. + */ + private Table agentSelection; + + /** + * Main composite. + */ + private Composite main; + + /** + * If any agent should be used. + */ + private Button allAgents; + + /** + * If specific agents should be used. + */ + private Button specificAgents; + + /** + * Cmr to get Agents from. + */ + private CmrRepositoryDefinition cmrRepositoryDefinition; + + /** + * Collection of agents that will be automatically selected in the wizard. + */ + private Collection autoSelectedAgents; + + /** + * Default constructor. + */ + public SelectAgentsWizardPage() { + this(DEFAULT_MESSAGE); + } + + /** + * This constructor sets the wizard page message. + * + * @param message + * Wizard page message. + */ + public SelectAgentsWizardPage(String message) { + this(message, Collections. emptyList()); + } + + /** + * This constructor sets the wizard page message and provides possibility to specify the agents + * that will be preselected if they are available on the repository. + * + * @param message + * Wizard page message. + * @param autoSelectedAgents + * Collection of agents that will be automatically selected in the wizard. + */ + public SelectAgentsWizardPage(String message, Collection autoSelectedAgents) { + super("Select Agent(s)"); + this.setTitle("Select Agent(s)"); + this.setMessage(message); + this.autoSelectedAgents = autoSelectedAgents; + } + + /** + * {@inheritDoc} + */ + @Override + public void createControl(Composite parent) { + main = new Composite(parent, SWT.NONE); + setControl(main); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPageComplete() { + if (null != allAgents && !allAgents.isDisposed() && allAgents.getSelection()) { + return true; + } else { + boolean agentSelected = false; + if (null != agentSelection && !agentSelection.isDisposed()) { + for (TableItem tableItem : agentSelection.getItems()) { + if (tableItem.getChecked()) { + agentSelected = true; + break; + } + } + } + if (!agentSelected) { + return false; + } + return true; + } + } + + /** + * Returns if all agents should be used. + * + * @return Returns if all agents should be used. + */ + public boolean isAllAgents() { + return allAgents.getSelection(); + } + + /** + * @return Returns list of Agent IDs to be involved in copy to buffer request. + */ + public List getSelectedAgents() { + if (allAgents.getSelection()) { + List returnList = new ArrayList(); + for (PlatformIdent agent : agentList) { + returnList.add(agent.getId()); + } + return returnList; + } else { + int index = 0; + List returnList = new ArrayList(); + for (TableItem tableItem : agentSelection.getItems()) { + if (tableItem.getChecked()) { + returnList.add(agentList.get(index).getId()); + } + index++; + } + return returnList; + } + } + + /** + * Sets the repository. Needed to be called before the page is displayed to the user. + * + * @param cmrRepositoryDefinition + * {@link CmrRepositoryDefinition}. + */ + public void setCmrRepositoryDefinition(final CmrRepositoryDefinition cmrRepositoryDefinition) { + if (!ObjectUtils.equals(cmrRepositoryDefinition, this.cmrRepositoryDefinition)) { + this.cmrRepositoryDefinition = cmrRepositoryDefinition; + for (Control control : main.getChildren()) { + control.dispose(); + } + + if (cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + Job getAgentsJob = new Job("Loading agents information..") { + @Override + protected IStatus run(IProgressMonitor monitor) { + final Map agentMap = cmrRepositoryDefinition.getGlobalDataAccessService().getAgentsOverview(); + agentList = new ArrayList(agentMap.keySet()); + Collections.sort(agentList, new Comparator() { + @Override + public int compare(PlatformIdent a1, PlatformIdent a2) { + return a1.getAgentName().compareToIgnoreCase(a2.getAgentName()); + } + }); + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + main.setLayout(new GridLayout(1, false)); + + allAgents = new Button(main, SWT.RADIO); + allAgents.setText("All agent(s)"); + allAgents.setSelection(true); + + specificAgents = new Button(main, SWT.RADIO); + specificAgents.setText("Select specific Agent(s)"); + + boolean preSelectedAgentsActive = false; + agentSelection = new Table(main, SWT.CHECK | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.FULL_SELECTION); + for (PlatformIdent platformIdent : agentList) { + AgentStatusData agentStatusData = agentMap.get(platformIdent); + TableItem tableItem = new TableItem(agentSelection, SWT.NONE); + tableItem.setText(TextFormatter.getAgentDescription(platformIdent, agentStatusData)); + tableItem.setImage(ImageFormatter.getAgentImage(agentStatusData)); + if (CollectionUtils.isNotEmpty(autoSelectedAgents) && autoSelectedAgents.contains(platformIdent)) { + tableItem.setChecked(true); + preSelectedAgentsActive = true; + } + } + agentSelection.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + agentSelection.setEnabled(false); + + Listener pageCompletedListener = new Listener() { + + @Override + public void handleEvent(Event event) { + setPageComplete(isPageComplete()); + } + }; + agentSelection.addListener(SWT.Selection, pageCompletedListener); + allAgents.addListener(SWT.Selection, pageCompletedListener); + specificAgents.addListener(SWT.Selection, pageCompletedListener); + + Listener agentsSelectionListener = new Listener() { + + @Override + public void handleEvent(Event event) { + if (allAgents.getSelection()) { + agentSelection.setEnabled(false); + } else { + agentSelection.setEnabled(true); + } + } + }; + allAgents.addListener(SWT.Selection, agentsSelectionListener); + specificAgents.addListener(SWT.Selection, agentsSelectionListener); + + if (preSelectedAgentsActive) { + specificAgents.setSelection(true); + allAgents.setSelection(false); + agentSelection.setEnabled(true); + } + + main.layout(); + } + }); + return Status.OK_STATUS; + } + }; + getAgentsJob.schedule(); + } else { + main.setLayout(new GridLayout(2, false)); + + new Label(main, SWT.NONE).setImage(Display.getDefault().getSystemImage(SWT.ERROR)); + Label text = new Label(main, SWT.WRAP); + text.setText("Selected repository is currently offline. Action can not be performed."); + main.layout(); + } + } + } + +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/SelectExistingStorageWizardPage.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/SelectExistingStorageWizardPage.java new file mode 100644 index 000000000..c307ca7c2 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/SelectExistingStorageWizardPage.java @@ -0,0 +1,271 @@ +package info.novatec.inspectit.rcp.wizard.page; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.storage.recording.RecordingState; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; + +/** + * Selection of existing storage. + * + * @author Ivan Senic + * + */ +public class SelectExistingStorageWizardPage extends WizardPage { + + /** + * Default page message. + */ + private static final String DEFAULT_MESSAGE = "Select storage and repository where data will be stored"; + + /** + * List of available repositories. + */ + private List cmrRepositories; + + /** + * {@link CmrRepositoryDefinition} that should be initially selected. + */ + private CmrRepositoryDefinition proposedCmrRepositoryDefinition; + + /** + * List of storages to be selected. + */ + private List storageList = new ArrayList(); + + /** + * Repository combo. + */ + private Combo cmrRepositoryCombo; + + /** + * Storage selection. + */ + private org.eclipse.swt.widgets.List storageSelection; + + /** + * If the recording check should be performed on the selected CMR. + */ + private boolean checkRecording; + + /** + * Button for choosing if storage should be auto finalized. + */ + private Button autoFinalize; + + /** + * Default constructor. + */ + public SelectExistingStorageWizardPage() { + super("Select Storage"); + this.setTitle("Select Storage"); + this.setMessage(DEFAULT_MESSAGE); + cmrRepositories = new ArrayList(); + cmrRepositories.addAll(InspectIT.getDefault().getCmrRepositoryManager().getCmrRepositoryDefinitions()); + } + + /** + * Additional constructor to specify the {@link #checkRecording} value. + * + * @param checkRecording + * If the recording check should be performed on the selected CMR. + */ + public SelectExistingStorageWizardPage(boolean checkRecording) { + this(); + this.checkRecording = checkRecording; + } + + /** + * With this constructor the passed {@link CmrRepositoryDefinition} will be initially selected + * on the page. + * + * @param proposedCmrRepositoryDefinition + * {@link CmrRepositoryDefinition} that should be initially selected. + * @param checkRecording + * If the recording check should be performed on the selected CMR. + */ + public SelectExistingStorageWizardPage(CmrRepositoryDefinition proposedCmrRepositoryDefinition, boolean checkRecording) { + this(); + this.proposedCmrRepositoryDefinition = proposedCmrRepositoryDefinition; + this.checkRecording = checkRecording; + } + + /** + * {@inheritDoc} + */ + public void createControl(Composite parent) { + Composite main = new Composite(parent, SWT.NONE); + main.setLayout(new GridLayout(2, false)); + + Label cmrSelectLabel = new Label(main, SWT.LEFT); + cmrSelectLabel.setText("Repository:"); + cmrRepositoryCombo = new Combo(main, SWT.READ_ONLY); + cmrRepositoryCombo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + Label storageLabel = new Label(main, SWT.TOP); + storageLabel.setText("Storage:"); + storageLabel.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + + storageSelection = new org.eclipse.swt.widgets.List(main, SWT.BORDER | SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL); + storageSelection.setEnabled(false); + storageSelection.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + new Label(main, SWT.LEFT); + autoFinalize = new Button(main, SWT.CHECK); + autoFinalize.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + autoFinalize.setText("Auto-finalize storage"); + autoFinalize.setToolTipText("If selected the storage will be automatically finalized after the action completes"); + autoFinalize.setSelection(true); + + final Listener pageCompletedListener = new Listener() { + @Override + public void handleEvent(Event event) { + setPageComplete(isPageComplete()); + if (getSelectedRepository() == null) { + setMessage("Repository must be selected.", IMessageProvider.ERROR); + } else if (getSelectedRepository().getOnlineStatus() == OnlineStatus.OFFLINE) { + setMessage("Selected repository is currently offline.", IMessageProvider.ERROR); + } else if (checkRecording && getSelectedRepository().getStorageService().getRecordingState() != RecordingState.OFF) { + setMessage("Recording is already active on selected repository.", IMessageProvider.ERROR); + } else if (getSelectedStorageData() == null) { + setMessage("Storage must be selected.", IMessageProvider.ERROR); + } else { + setMessage(DEFAULT_MESSAGE); + } + } + }; + cmrRepositoryCombo.addListener(SWT.Selection, new Listener() { + @Override + public void handleEvent(Event event) { + updateStorageList(); + pageCompletedListener.handleEvent(event); + } + }); + + storageSelection.addListener(SWT.Selection, pageCompletedListener); + + int i = 0; + int index = -1; + for (CmrRepositoryDefinition cmrRepositoryDefinition : cmrRepositories) { + cmrRepositoryCombo.add(cmrRepositoryDefinition.getName() + " (" + cmrRepositoryDefinition.getIp() + ":" + cmrRepositoryDefinition.getName() + ")"); + if (cmrRepositoryDefinition.equals(proposedCmrRepositoryDefinition)) { + index = i; + } + i++; + } + if (index != -1) { + cmrRepositoryCombo.select(index); + cmrRepositoryCombo.setEnabled(false); + updateStorageList(); + } + + setControl(main); + } + + /** + * @return Returns selected repository or null. + */ + public CmrRepositoryDefinition getSelectedRepository() { + if (cmrRepositoryCombo.getSelectionIndex() != -1) { + return cmrRepositories.get(cmrRepositoryCombo.getSelectionIndex()); + } + return null; + } + + /** + * @return Returns selected {@link StorageData}. + */ + public StorageData getSelectedStorageData() { + if (storageSelection.getSelectionIndex() != -1) { + return storageList.get(storageSelection.getSelectionIndex()); + } + return null; + } + + /** + * Returns if auto-finalize options is selected. + * + * @return Returns if auto-finalize options is selected. + */ + public boolean isAutoFinalize() { + return autoFinalize.getSelection(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPageComplete() { + if (getSelectedRepository() == null) { + return false; + } else if (getSelectedRepository().getOnlineStatus() == OnlineStatus.OFFLINE) { + return false; + } else if (checkRecording && getSelectedRepository().getStorageService().getRecordingState() != RecordingState.OFF) { + return false; + } + if (getSelectedStorageData() == null) { + return false; + } + return true; + } + + /** + * Updates the storage list based on selected repository. + */ + private void updateStorageList() { + final CmrRepositoryDefinition cmrRepositoryDefinition = getSelectedRepository(); + Job updateStoragesJob = new Job("Updating Storages") { + @Override + protected IStatus run(IProgressMonitor monitor) { + if (null != cmrRepositoryDefinition && cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + storageList = cmrRepositoryDefinition.getStorageService().getOpenedStorages(); + } else { + storageList = Collections.emptyList(); + } + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + storageSelection.removeAll(); + if (null != cmrRepositoryDefinition && cmrRepositoryDefinition.getOnlineStatus() != OnlineStatus.OFFLINE) { + if (storageList.isEmpty()) { + storageSelection.add("No open storage available for writing"); + storageSelection.setEnabled(false); + } else { + for (StorageData storageData : storageList) { + storageSelection.add(storageData.getName()); + } + storageSelection.setEnabled(true); + } + } else { + storageSelection.setEnabled(false); + } + } + }); + return Status.OK_STATUS; + } + }; + updateStoragesJob.schedule(); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/StorageCompressionWizardPage.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/StorageCompressionWizardPage.java new file mode 100644 index 000000000..dd73d20ec --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/StorageCompressionWizardPage.java @@ -0,0 +1,64 @@ +package info.novatec.inspectit.rcp.wizard.page; + +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; + +/** + * Page for the selecting if storage should be compressed before downloading/exporting etc.. + * + * @author Ivan Senic + * + */ +public class StorageCompressionWizardPage extends WizardPage { + + /** + * If compression should be used. + */ + private Button compress; + + /** + * Default constructor. + * + * @param title + * the title. + * @param message + * the message. + */ + public StorageCompressionWizardPage(String title, String message) { + super(title); + this.setTitle(title); + this.setMessage(message); + } + + /** + * {@inheritDoc} + */ + @Override + public void createControl(Composite parent) { + Composite main = new Composite(parent, SWT.NONE); + main.setLayout(new GridLayout(1, false)); + + new Label(main, SWT.NONE).setText("Compress the data before transfer:"); + + compress = new Button(main, SWT.RADIO); + compress.setText("Yes - suggested if transferring from/to Internet or slow network"); + compress.setSelection(true); + + Button dontCompress = new Button(main, SWT.RADIO); + dontCompress.setText("No - suggested if transferring from/to from fast local network"); + dontCompress.setSelection(false); + + setControl(main); + } + + /** + * @return If user selected to compress data before download. + */ + public boolean isCompressBefore() { + return compress.getSelection(); + } +} diff --git a/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/UploadStorageWizardPage.java b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/UploadStorageWizardPage.java new file mode 100644 index 000000000..94b0e5ae1 --- /dev/null +++ b/inspectIT/src/info/novatec/inspectit/rcp/wizard/page/UploadStorageWizardPage.java @@ -0,0 +1,214 @@ +package info.novatec.inspectit.rcp.wizard.page; + +import info.novatec.inspectit.rcp.InspectIT; +import info.novatec.inspectit.rcp.composite.StorageInfoComposite; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; +import info.novatec.inspectit.storage.LocalStorageData; +import info.novatec.inspectit.storage.StorageData; +import info.novatec.inspectit.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; + +/** + * Page for selecting CMR to upload storage to. + * + * @author Ivan Senic + * + */ +public class UploadStorageWizardPage extends WizardPage { + + /** + * Default page message. + */ + private static final String DEFAULT_MESSAGE = "Select CMR (Central Management Repository) to upload storage to"; + + /** + * Local data to upload. + */ + private LocalStorageData localStorageData; + + /** + * List of CMR repositories. + */ + private List cmrRepositories; + + /** + * Combo that displays the CMRs. + */ + private Combo cmrRepositoryCombo; + + /** + * If storage is already available on the CMR. + */ + private boolean alreadyAvailable; + + /** + * Is there enough space on the CMR. + */ + private boolean enoughSpace; + + /** + * Storages on selected repository. + */ + private List storagesOnRepository; + + /** + * Space left on the selected repository. + */ + private long spaceLeftOnCmr; + + /** + * Default constructor. + * + * @param localStorageData + * Local data to upload. Must not be null. + */ + public UploadStorageWizardPage(LocalStorageData localStorageData) { + super("Upload Storage"); + Assert.isNotNull(localStorageData); + this.localStorageData = localStorageData; + this.cmrRepositories = new ArrayList(); + this.cmrRepositories.addAll(InspectIT.getDefault().getCmrRepositoryManager().getCmrRepositoryDefinitions()); + this.setTitle("Upload Storage"); + this.setMessage(DEFAULT_MESSAGE); + } + + /** + * {@inheritDoc} + */ + @Override + public void createControl(Composite parent) { + Composite main = new Composite(parent, SWT.NONE); + main.setLayout(new GridLayout(2, false)); + + Label cmrSelectLabel = new Label(main, SWT.LEFT); + cmrSelectLabel.setText("Upload CMR:"); + cmrRepositoryCombo = new Combo(main, SWT.READ_ONLY); + cmrRepositoryCombo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + for (CmrRepositoryDefinition cmrRepositoryDefinition : cmrRepositories) { + cmrRepositoryCombo.add(cmrRepositoryDefinition.getName() + " (" + cmrRepositoryDefinition.getIp() + ":" + cmrRepositoryDefinition.getPort() + ")"); + } + + StorageInfoComposite storageInfoComposite = new StorageInfoComposite(main, SWT.NONE, false, localStorageData); + storageInfoComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); + + final Listener pageCompleteListener = new Listener() { + @Override + public void handleEvent(Event event) { + boolean isPageComplete = isPageComplete(); + setPageComplete(isPageComplete); + if (event.widget.equals(cmrRepositoryCombo) && cmrRepositoryCombo.getSelectionIndex() == -1) { + setMessage("Repository must be selected", IMessageProvider.ERROR); + return; + } else if (event.widget.equals(cmrRepositoryCombo) && getCmrRepositoryDefinition().getOnlineStatus() == OnlineStatus.OFFLINE) { + setMessage("The selected repository is currently unavailable", IMessageProvider.ERROR); + } else if (alreadyAvailable) { + setMessage("The selected storage to upload is already available on the selected repository", IMessageProvider.ERROR); + } else if (!enoughSpace) { + setMessage("Insufficient storage space left on the selected repository", ERROR); + } else { + String cmrVersion = getCmrRepositoryDefinition().getVersion(); + if (null == localStorageData.getCmrVersion()) { + setMessage("Selected storage does not define CMR version. The storage might be unstable on the CMR version " + cmrVersion + ".", WARNING); + } else if (!ObjectUtils.equals(localStorageData.getCmrVersion(), cmrVersion)) { + setMessage("Selected storage has different CMR version than the current CMR version " + cmrVersion + ". The storage might be unstable.", WARNING); + } else { + setMessage(DEFAULT_MESSAGE); + } + } + } + }; + + Listener cmrComboListener = new Listener() { + @Override + public void handleEvent(final Event event) { + final CmrRepositoryDefinition cmrRepositoryDefinition = getCmrRepositoryDefinition(); + Job updateCmrData = new Job("Loading CMR Storage Data") { + @Override + protected IStatus run(IProgressMonitor monitor) { + OnlineStatus onlineStatus = cmrRepositoryDefinition.getOnlineStatus(); + if (onlineStatus != OnlineStatus.OFFLINE) { + storagesOnRepository = cmrRepositoryDefinition.getStorageService().getExistingStorages(); + spaceLeftOnCmr = cmrRepositoryDefinition.getCmrManagementService().getCmrStatusData().getStorageDataSpaceLeft(); + } else { + storagesOnRepository = Collections.emptyList(); + spaceLeftOnCmr = 0; + } + + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + pageCompleteListener.handleEvent(event); + } + }); + return Status.OK_STATUS; + } + }; + updateCmrData.schedule(); + } + }; + + cmrRepositoryCombo.addListener(SWT.Selection, cmrComboListener); + + setControl(main); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPageComplete() { + if (cmrRepositoryCombo.getSelectionIndex() == -1) { + return false; + } else if (getCmrRepositoryDefinition().getOnlineStatus() == OnlineStatus.OFFLINE) { + return false; + } + for (StorageData storageData : storagesOnRepository) { + if (Objects.equals(storageData.getId(), localStorageData.getId())) { + alreadyAvailable = true; + return false; + } + } + alreadyAvailable = false; + + enoughSpace = spaceLeftOnCmr > localStorageData.getDiskSize(); + if (!enoughSpace) { + return false; + } + + return true; + } + + /** + * @return Return selected CMR repository. + */ + public CmrRepositoryDefinition getCmrRepositoryDefinition() { + if (cmrRepositoryCombo.getSelectionIndex() != -1) { + return cmrRepositories.get(cmrRepositoryCombo.getSelectionIndex()); + } + return null; + } + +} diff --git a/inspectIT/test/info/novatec/inspectit/rcp/editor/preferences/control/samplingrate/test/TimeframeDividerTest.java b/inspectIT/test/info/novatec/inspectit/rcp/editor/preferences/control/samplingrate/test/TimeframeDividerTest.java new file mode 100644 index 000000000..e7e0fedd6 --- /dev/null +++ b/inspectIT/test/info/novatec/inspectit/rcp/editor/preferences/control/samplingrate/test/TimeframeDividerTest.java @@ -0,0 +1,287 @@ +package info.novatec.inspectit.rcp.editor.preferences.control.samplingrate.test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.lessThan; +import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import info.novatec.inspectit.communication.DefaultData; +import info.novatec.inspectit.communication.data.ClassLoadingInformationData; +import info.novatec.inspectit.indexing.aggregation.impl.ClassLoadingInformationDataAggregator; +import info.novatec.inspectit.rcp.editor.preferences.control.SamplingRateControl; +import info.novatec.inspectit.rcp.editor.preferences.control.SamplingRateControl.Sensitivity; +import info.novatec.inspectit.rcp.editor.preferences.control.samplingrate.SamplingRateMode; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +public class TimeframeDividerTest { + + /** + * The list with data objects. + */ + private List dataObjects; + + /** + * The sampling rate mode. + */ + SamplingRateMode mode = SamplingRateMode.TIMEFRAME_DIVIDER; + + @BeforeTest + public void initTestClass() { + dataObjects = createDataObjects(50); + } + + /** + * Creates countOfObjects data objects. + * + * @param countOfObjects + * The count of objects to create. + * @return The list with data objects. + */ + public List createDataObjects(int countOfObjects) { + List tempObjects = new ArrayList(); + + // the time of the first data object is Mon Sep 15 11:00:00 CEST 2008 + long currentTime = 1221469200000L; + + for (int i = 0; i < countOfObjects; i++) { + ClassLoadingInformationData data = new ClassLoadingInformationData(new Timestamp(currentTime), 1, 5); + currentTime += 5000L; + int count = 5; + data.setCount(count); + data.setId(-1L); + data.setMinLoadedClassCount(2000 + i); + data.setMaxLoadedClassCount(3000 + i); + int totalLoadedClassCount = (((data.getMinLoadedClassCount() + data.getMaxLoadedClassCount())) / 2) / count; + data.setTotalLoadedClassCount(totalLoadedClassCount); + + data.setMinUnloadedClassCount(20 + i); + data.setMaxUnloadedClassCount(30 + i); + long totalUnloadedClassCount = ((data.getMinUnloadedClassCount() + data.getMaxUnloadedClassCount()) / 2) / count; + data.setTotalUnloadedClassCount(totalUnloadedClassCount); + + data.setMinTotalLoadedClassCount(10000); + data.setMaxTotalLoadedClassCount(15000); + long totalTotalLoadedClassCount = ((data.getMinTotalLoadedClassCount() + data.getMaxTotalLoadedClassCount()) / 2) / count; + data.setTotalTotalLoadedClassCount(totalTotalLoadedClassCount); + + tempObjects.add(data); + } + + return tempObjects; + } + + /** + * When the passed data object list isn't empty, then the result list shouldn't be empty too. + */ + @Test + public void resultValuesExist() { + List resultList = null; + Sensitivity sensitivity = SamplingRateControl.Sensitivity.VERY_COARSE; + + // from time is Mon Sep 15 10:50:12 CEST 2008 + Date fromDate = new Date(1221468612000L); + + // to time is Mon Sep 15 12:00:00 CEST 2008 + Date toDate = new Date(1221472800000L); + + resultList = mode.adjustSamplingRate(dataObjects, fromDate, toDate, sensitivity.getValue(), new ClassLoadingInformationDataAggregator()); + + assertThat(resultList, is(notNullValue())); + } + + /** + * When nine values are in one timeframe and one value is in another timeframe, then the result + * is 2. + */ + @Test() + public void nineValuesInOneTimeframe() { + List tempList = new ArrayList(); + List resultList = null; + Sensitivity sensitivity = SamplingRateControl.Sensitivity.VERY_COARSE; + + // from time is Mon Sep 15 11:00:00 CEST 2008 + Date fromDate = new Date(1221469200000L); + + // to time is Mon Sep 15 11:20:00 CEST 2008 + Date toDate = new Date(1221470400000L); + + // create nine data objects + tempList = createDataObjects(9); + + // create one data object + ClassLoadingInformationData data = new ClassLoadingInformationData(new Timestamp(fromDate.getTime() + 300000L), 1, 5); + int count = 5; + data.setCount(count); + data.setId(-1L); + data.setMinLoadedClassCount(2010); + data.setMaxLoadedClassCount(3010); + int totalLoadedClassCount = (((data.getMinLoadedClassCount() + data.getMaxLoadedClassCount())) / 2) / count; + data.setTotalLoadedClassCount(totalLoadedClassCount); + + data.setMinUnloadedClassCount(30); + data.setMaxUnloadedClassCount(40); + long totalUnloadedClassCount = ((data.getMinUnloadedClassCount() + data.getMaxUnloadedClassCount()) / 2) / count; + data.setTotalUnloadedClassCount(totalUnloadedClassCount); + + data.setMinTotalLoadedClassCount(10000); + data.setMaxTotalLoadedClassCount(15000); + long totalTotalLoadedClassCount = ((data.getMinTotalLoadedClassCount() + data.getMaxTotalLoadedClassCount()) / 2) / count; + data.setTotalTotalLoadedClassCount(totalTotalLoadedClassCount); + + tempList.add(data); + + resultList = mode.adjustSamplingRate(tempList, fromDate, toDate, sensitivity.getValue(), new ClassLoadingInformationDataAggregator()); + + assertThat(resultList.size(), is(equalTo(2))); + } + + /** + * No data to aggregate when an empty list is passed. + */ + @Test + public void emptyListAsParameter() { + List resultList = null; + Sensitivity sensitivity = SamplingRateControl.Sensitivity.VERY_COARSE; + + // from time is Mon Sep 15 10:50:12 CEST 2008 + Date fromDate = new Date(1221468612000L); + + // to time is Mon Sep 15 12:00:00 CEST 2008 + Date toDate = new Date(1221472800000L); + + resultList = mode.adjustSamplingRate(null, fromDate, toDate, sensitivity.getValue(), new ClassLoadingInformationDataAggregator()); + + assertThat(resultList, is(nullValue())); + } + + /** + * No data to aggregate when sampling rate is bigger than the count of data objects. + */ + @Test + public void noDataToAggregateWithLessObjectsThanSamplingRate() { + List resultList = null; + List tempList = createDataObjects(30); + Sensitivity sensitivity = SamplingRateControl.Sensitivity.MEDIUM; + + // from time is Mon Sep 15 10:50:12 CEST 2008 + Date fromDate = new Date(1221468612000L); + + // to time is Mon Sep 15 12:00:00 CEST 2008 + Date toDate = new Date(1221472800000L); + + resultList = mode.adjustSamplingRate(tempList, fromDate, toDate, sensitivity.getValue(), new ClassLoadingInformationDataAggregator()); + + assertThat(resultList.size(), is(lessThan(sensitivity.getValue()))); + } + + /** + * No data to aggregate when no sampling rate is set. + */ + @Test + public void noDataToAggregateWhenNoSamplingRateIsSet() { + List resultList = null; + + // from time is Mon Sep 15 10:50:12 CEST 2008 + Date fromDate = new Date(1221468612000L); + + // to time is Mon Sep 15 12:00:00 CEST 2008 + Date toDate = new Date(1221472800000L); + + resultList = mode.adjustSamplingRate(dataObjects, fromDate, toDate, 0, new ClassLoadingInformationDataAggregator()); + + assertThat((Object) dataObjects, is(equalTo((Object) resultList))); + } + + /** + * All aggregated values must lie in between from and to. + */ + @Test + public void aggregatedValuesBetweenFromTo() { + List resultList = null; + Sensitivity sensitivity = SamplingRateControl.Sensitivity.VERY_COARSE; + + // from time is Mon Sep 15 10:50:12 CEST 2008 + Date fromDate = new Date(1221468612000L); + + // to time is Mon Sep 15 12:00:00 CEST 2008 + Date toDate = new Date(1221472800000L); + + resultList = mode.adjustSamplingRate(dataObjects, fromDate, toDate, sensitivity.getValue(), new ClassLoadingInformationDataAggregator()); + + for (DefaultData defaultData : resultList) { + long dataTime = defaultData.getTimeStamp().getTime(); + assertThat(dataTime, is(greaterThanOrEqualTo(fromDate.getTime()))); + assertThat(dataTime, is(lessThanOrEqualTo(toDate.getTime()))); + } + } + + /** + * If sampling rate is set, then values should be aggregated. + */ + @Test + public void samplingRateIsSet() { + List resultList = null; + Sensitivity sensitivity = SamplingRateControl.Sensitivity.VERY_COARSE; + + // from time is Mon Sep 15 10:50:12 CEST 2008 + Date fromDate = new Date(1221468612000L); + + // to time is Mon Sep 15 12:00:00 CEST 2008 + Date toDate = new Date(1221472800000L); + + resultList = mode.adjustSamplingRate(dataObjects, fromDate, toDate, sensitivity.getValue(), new ClassLoadingInformationDataAggregator()); + + assertThat(resultList.size(), is(lessThanOrEqualTo(sensitivity.getValue()))); + } + + /** + * If the from and to time are smaller then the time of the first data object, then no data + * should be aggregated. + */ + @Test + public void fromAndToBeforeDataExists() { + List resultList = null; + Sensitivity sensitivity = SamplingRateControl.Sensitivity.VERY_COARSE; + + // the fromDate is Mon Sep 15 10:00:00 CEST 2008 + Date fromDate = new Date(1221465600000L); + + // the toDate is Mon Sep 15 10:30:00 CEST 2008 + Date toDate = new Date(1221467400000L); + + resultList = mode.adjustSamplingRate(dataObjects, fromDate, toDate, sensitivity.getValue(), new ClassLoadingInformationDataAggregator()); + + assertThat(resultList.size(), is(equalTo(0))); + } + + /** + * If all values are in one timeframe, then the result must be 1. + */ + @Test + public void allValuesInOneTimeframe() { + List resultList = null; + Sensitivity sensitivity = SamplingRateControl.Sensitivity.VERY_COARSE; + + // from time is Sun Sep 14 06:00:00 CEST 2008 + Date fromDate = new Date(1221364800000L); + + // to time is Mon Sep 15 12:00:00 CEST 2008 + Date toDate = new Date(1221472800000L); + + resultList = mode.adjustSamplingRate(dataObjects, fromDate, toDate, sensitivity.getValue(), new ClassLoadingInformationDataAggregator()); + + assertThat(resultList.size(), is(equalTo(1))); + } + +} diff --git a/inspectIT/test/info/novatec/inspectit/rcp/editor/search/factory/SearchFactoryTest.java b/inspectIT/test/info/novatec/inspectit/rcp/editor/search/factory/SearchFactoryTest.java new file mode 100644 index 000000000..dda7f3761 --- /dev/null +++ b/inspectIT/test/info/novatec/inspectit/rcp/editor/search/factory/SearchFactoryTest.java @@ -0,0 +1,302 @@ +package info.novatec.inspectit.rcp.editor.search.factory; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import info.novatec.inspectit.cmr.model.MethodIdent; +import info.novatec.inspectit.cmr.service.cache.CachedDataService; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.HttpTimerData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.rcp.editor.search.criteria.SearchCriteria; +import info.novatec.inspectit.rcp.repository.RepositoryDefinition; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Tests the {@link SearchFactory} class. + * + * @author Ivan Senic + * + */ +@SuppressWarnings("PMD") +public class SearchFactoryTest { + + /** + * {@link RepositoryDefinition}. + */ + @Mock + private RepositoryDefinition repositoryDefinition; + + /** + * Init method. + */ + @BeforeMethod + public void initMocks() { + MockitoAnnotations.initMocks(this); + + MethodIdent methodIdent = mock(MethodIdent.class); + when(methodIdent.getId()).thenReturn(1L); + when(methodIdent.getFQN()).thenReturn(""); + when(methodIdent.getMethodName()).thenReturn(""); + when(methodIdent.getParameters()).thenReturn(Collections. emptyList()); + + CachedDataService cachedDataService = mock(CachedDataService.class); + when(repositoryDefinition.getCachedDataService()).thenReturn(cachedDataService); + when(cachedDataService.getMethodIdentForId(1L)).thenReturn(methodIdent); + } + + /** + * Tests if passed element is null, return is always false. + */ + @Test + public void nullElementSearch() { + SearchCriteria searchCriteria = new SearchCriteria(""); + assertThat(SearchFactory.isSearchCompatible(null, searchCriteria, repositoryDefinition), is(equalTo(false))); + } + + /** + * Tests that method ident is properly searched. + */ + @Test + public void methodIdentSearch() { + TimerData timerData = new TimerData(); + timerData.setMethodIdent(1L); + + MethodIdent methodIdent = repositoryDefinition.getCachedDataService().getMethodIdentForId(1L); + + SearchCriteria searchCriteria = new SearchCriteria("Blah"); + SearchCriteria wrong = new SearchCriteria("halB"); + + when(methodIdent.getFQN()).thenReturn("blah.blah.blah"); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(timerData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(timerData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(timerData, wrong, repositoryDefinition), is(equalTo(false))); + + when(methodIdent.getFQN()).thenReturn(""); + when(methodIdent.getMethodName()).thenReturn("balhblah"); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(timerData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(timerData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(timerData, wrong, repositoryDefinition), is(equalTo(false))); + + when(methodIdent.getMethodName()).thenReturn(""); + List params = new ArrayList(); + params.add("blaha"); + when(methodIdent.getParameters()).thenReturn(params); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(timerData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(timerData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(timerData, wrong, repositoryDefinition), is(equalTo(false))); + } + + /** + * Tests that the {@link SqlStatementData} is searched correctly. + */ + @Test + public void sqlStatementDataSearch() { + SqlStatementData sqlData = new SqlStatementData(); + sqlData.setMethodIdent(1L); + sqlData.setSql("Select blah from table where condition"); + + SearchCriteria searchCriteria = new SearchCriteria("Blah"); + SearchCriteria wrong = new SearchCriteria("halB"); + + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(sqlData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(sqlData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(sqlData, wrong, repositoryDefinition), is(equalTo(false))); + + List parameters = new ArrayList(); + parameters.add("blah"); + sqlData.setSql("Select somthing from table where condition=?"); + sqlData.setParameterValues(parameters); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(sqlData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(sqlData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(sqlData, wrong, repositoryDefinition), is(equalTo(false))); + + } + + /** + * Tests that the {@link HttpTimerData} is searched correctly. + */ + @Test + public void httpTimerDataSearch() { + HttpTimerData httpData = new HttpTimerData(); + httpData.setMethodIdent(1L); + + SearchCriteria searchCriteria = new SearchCriteria("Blah"); + SearchCriteria wrong = new SearchCriteria("halB"); + + httpData.setInspectItTaggingHeaderValue("blaha"); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(httpData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(httpData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(httpData, wrong, repositoryDefinition), is(equalTo(false))); + + httpData.setInspectItTaggingHeaderValue(""); + httpData.setUri("ablah"); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(httpData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(httpData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(httpData, wrong, repositoryDefinition), is(equalTo(false))); + + httpData.setUri(""); + httpData.setRequestMethod("ablaha"); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(httpData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(httpData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(httpData, wrong, repositoryDefinition), is(equalTo(false))); + + Map map = new HashMap(); + map.put("ablaha", "value"); + httpData.setRequestMethod(""); + httpData.setAttributes(map); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(httpData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(httpData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(httpData, wrong, repositoryDefinition), is(equalTo(false))); + + httpData.setAttributes(Collections. emptyMap()); + httpData.setSessionAttributes(map); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(httpData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(httpData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(httpData, wrong, repositoryDefinition), is(equalTo(false))); + + httpData.setSessionAttributes(Collections. emptyMap()); + map.clear(); + map.put("key", "ablaha"); + httpData.setHeaders(map); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(httpData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(httpData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(httpData, wrong, repositoryDefinition), is(equalTo(false))); + + httpData.setHeaders(Collections. emptyMap()); + Map map1 = new HashMap(); + map1.put("key", new String[] { "blah", "anotherValue" }); + httpData.setParameters(map1); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(httpData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(httpData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(httpData, wrong, repositoryDefinition), is(equalTo(false))); + } + + /** + * Tests that the {@link ExceptionSensorData} is searched correctly. + */ + @Test + public void exceptionDataSearch() { + ExceptionSensorData exceptionData = new ExceptionSensorData(); + exceptionData.setMethodIdent(1L); + + SearchCriteria searchCriteria = new SearchCriteria("Blah"); + SearchCriteria wrong = new SearchCriteria("halB"); + + exceptionData.setCause("blah"); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(exceptionData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(exceptionData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(exceptionData, wrong, repositoryDefinition), is(equalTo(false))); + + exceptionData.setCause(""); + exceptionData.setThrowableType("blah.bla"); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(exceptionData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(exceptionData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(exceptionData, wrong, repositoryDefinition), is(equalTo(false))); + + exceptionData.setThrowableType(""); + exceptionData.setErrorMessage("My very blah error message"); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(exceptionData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(exceptionData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(exceptionData, wrong, repositoryDefinition), is(equalTo(false))); + + exceptionData.setErrorMessage(""); + exceptionData.setStackTrace("10: java blah"); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(exceptionData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(exceptionData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(exceptionData, wrong, repositoryDefinition), is(equalTo(false))); + } + + /** + * Tests that the {@link InvocationSequenceData} is searched correctly. + */ + @Test + public void invocationDataSearch() { + InvocationSequenceData invocationData = new InvocationSequenceData(); + invocationData.setMethodIdent(1L); + + SearchCriteria searchCriteria = new SearchCriteria("Blah"); + SearchCriteria wrong = new SearchCriteria("halB"); + + HttpTimerData httpData = new HttpTimerData(); + httpData.setMethodIdent(1L); + httpData.setInspectItTaggingHeaderValue("blaha"); + invocationData.setTimerData(httpData); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(httpData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(httpData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(httpData, wrong, repositoryDefinition), is(equalTo(false))); + + invocationData.setTimerData(null); + SqlStatementData sqlData = new SqlStatementData(); + sqlData.setMethodIdent(1L); + sqlData.setSql("Select blah from table where condition"); + invocationData.setSqlStatementData(sqlData); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(invocationData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(invocationData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(invocationData, wrong, repositoryDefinition), is(equalTo(false))); + + invocationData.setSqlStatementData(null); + ExceptionSensorData exceptionData = new ExceptionSensorData(); + exceptionData.setMethodIdent(1L); + exceptionData.setCause("blah"); + List exceptionList = new ArrayList(); + exceptionList.add(exceptionData); + invocationData.setExceptionSensorDataObjects(exceptionList); + searchCriteria.setCaseSensitive(false); + assertThat(SearchFactory.isSearchCompatible(invocationData, searchCriteria, repositoryDefinition), is(equalTo(true))); + searchCriteria.setCaseSensitive(true); + assertThat(SearchFactory.isSearchCompatible(invocationData, searchCriteria, repositoryDefinition), is(equalTo(false))); + assertThat(SearchFactory.isSearchCompatible(invocationData, wrong, repositoryDefinition), is(equalTo(false))); + } + +} diff --git a/inspectITJMeter/.checkstyle b/inspectITJMeter/.checkstyle new file mode 100644 index 000000000..90e703e66 --- /dev/null +++ b/inspectITJMeter/.checkstyle @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/inspectITJMeter/.classpath b/inspectITJMeter/.classpath new file mode 100644 index 000000000..63c609282 --- /dev/null +++ b/inspectITJMeter/.classpath @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/inspectITJMeter/.gitignore b/inspectITJMeter/.gitignore new file mode 100644 index 000000000..648ce684f --- /dev/null +++ b/inspectITJMeter/.gitignore @@ -0,0 +1,8 @@ +/bin +/dist +/lib +/build +/resources/java15runtime/ +/reports +/release +/test-output \ No newline at end of file diff --git a/inspectITJMeter/.pmd b/inspectITJMeter/.pmd new file mode 100644 index 000000000..863bff9e8 --- /dev/null +++ b/inspectITJMeter/.pmd @@ -0,0 +1,7 @@ + + + true + shared/config/pmd/pmd_rules.xml + false + false + diff --git a/inspectITJMeter/.project b/inspectITJMeter/.project new file mode 100644 index 000000000..197b306b3 --- /dev/null +++ b/inspectITJMeter/.project @@ -0,0 +1,46 @@ + + + inspectITJMeter + + + + + + org.eclipse.jdt.core.javabuilder + + + + + net.sf.eclipsecs.core.CheckstyleBuilder + + + + + + org.eclipse.jdt.core.javanature + org.apache.ivyde.eclipse.ivynature + net.sf.eclipsecs.core.CheckstyleNature + + + + inspectITSpringConfig + 2 + inspectITSpringConfig + + + shared + 2 + shared + + + + + inspectITSpringConfig + $%7BPARENT-1-PROJECT_LOC%7D/inspectIT/META-INF/spring + + + shared + $%7BPARENT-1-PROJECT_LOC%7D/Commons/resources/shared + + + diff --git a/inspectITJMeter/.settings/org.apache.ivyde.eclipse.prefs b/inspectITJMeter/.settings/org.apache.ivyde.eclipse.prefs new file mode 100644 index 000000000..6ef82e919 --- /dev/null +++ b/inspectITJMeter/.settings/org.apache.ivyde.eclipse.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.apache.ivyde.eclipse.standaloneretrieve= diff --git a/inspectITJMeter/.settings/org.eclipse.jdt.core.prefs b/inspectITJMeter/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..fcadc2981 --- /dev/null +++ b/inspectITJMeter/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore \ No newline at end of file diff --git a/inspectITJMeter/build.properties b/inspectITJMeter/build.properties new file mode 100644 index 000000000..a171d266c --- /dev/null +++ b/inspectITJMeter/build.properties @@ -0,0 +1,64 @@ +## Ivy +ivy.file = ${basedir}/resources/ivy/ivy.xml + +## Settings for TestNG +testng.haltonfailure=true +resources.testng=${basedir}/resources/testng + +## Some QA settings +build.root=${basedir}/build +build.classes.root=${build.root}/classes +build.overwrite.classes=${build.root}/overwriteclasses + +build.release.root=${basedir}/release +build.release.sampler=${build.release.root}/sampler +build.release.dependencies=${build.release.root}/dependencies + +## Some general settings +build.root=${basedir}/build +build.classes.root=${build.root}/classes +build.release.root=${build.root}/release +build.qa.root=${build.root}/QA +build.qa.test=${build.qa.root}/functional_tests +build.qa.test.testdata=${build.qa.test}/testdata +build.qa.test.coveragedata=${build.qa.test}/coveragedata +build.qa.analysis=${build.qa.root}/static_analysis +build.qa.analysis.pmd=${build.qa.analysis}/pmd +build.qa.analysis.findbugs=${build.qa.analysis}/findbugs +build.qa.analysis.checkstyle=${build.qa.analysis}/checkstyle +build.qa.analysis.cpd=${build.qa.analysis}/cpd +build.scripts=${build.root}/scripts +build.test.classes=${build.classes.root}/test + +##general settings +build.common-targets.file=${commons.basedir}/resources/shared/build/common-targets.xml +src.root=${basedir}/src +overwrite.src.root=${basedir}/overwrite +build.instrumented.classes=${build.classes.root}/instrumented +src.root=${basedir}/src +test.root=${basedir}/test +release.root=${basedir}/release +lib.root=${basedir}/lib + +java15runtime.path=${basedir}/resources/java15runtime + +## Setting for CommonsCS +commonscs.basedir=${basedir}/../CommonsCS +commonscs.build.release=${commonscs.basedir}/build/release +build.commonscs.classes=${commonscs.basedir}/build/classes/commonscs/classes +build.commonscs.file=${commonscs.basedir}/resources/build.xml +ivy.file.commonscs=${commonscs.basedir}/resources/ivy/ivy.xml + +## Setting for Commons +commons.basedir=${basedir}/../Commons +commons.build.release=${commons.basedir}/build/release +build.commons.classes=${commons.basedir}/build/classes/commons/classes +build.commons.file=${commons.basedir}/resources/build.xml +ivy.file.commons=${commons.basedir}/resources/ivy/ivy.xml + +## Setting for inspectIT +build.inspectit.basedir=${basedir}/../inspectIT +build.inspectit.file=${build.inspectit.basedir}/release/build.xml +build.inspectit.classes=${build.inspectit.basedir}/release/build/classes/rcp +build.inspectit.spring.context.model.main=${build.inspectit.basedir}/META-INF/spring/spring-context-model-main.xml +ivy.file.inspectit=${build.inspectit.basedir}/release/resources/ivy/ivy.xml diff --git a/inspectITJMeter/build.xml b/inspectITJMeter/build.xml new file mode 100644 index 000000000..def527a0e --- /dev/null +++ b/inspectITJMeter/build.xml @@ -0,0 +1,331 @@ + + + + + Sophisticated Monitoring tool by NovaTec GmbH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inspectITJMeter/overwrite/config/logback.xml b/inspectITJMeter/overwrite/config/logback.xml new file mode 100644 index 000000000..f0cc8dd94 --- /dev/null +++ b/inspectITJMeter/overwrite/config/logback.xml @@ -0,0 +1,25 @@ + + + + + + + System.out + + %d{ISO8601}: %-6r [%15.15t] %-5p %30.30c - %m%n + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/inspectITJMeter/overwrite/info/novatec/inspectit/rcp/InspectIT.java b/inspectITJMeter/overwrite/info/novatec/inspectit/rcp/InspectIT.java new file mode 100644 index 000000000..df2c9e082 --- /dev/null +++ b/inspectITJMeter/overwrite/info/novatec/inspectit/rcp/InspectIT.java @@ -0,0 +1,102 @@ +package info.novatec.inspectit.rcp; + +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.access.BeanFactoryLocator; +import org.springframework.beans.factory.access.BeanFactoryReference; +import org.springframework.context.access.ContextSingletonBeanFactoryLocator; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.joran.spi.JoranException; +import ch.qos.logback.core.util.StatusPrinter; + +/** + * Overwrites the InspectIT class from the inspectIT project. This allows us to re-use + * most of the code of the inspectIT project to access the CMR services. + * + * Please note that this class must "overwrite" the old inspectIT class by prepending it to the + * classpath. + * + * @author Stefan Siegl + */ +// NOCHKALL +@SuppressWarnings("PMD") +public class InspectIT { + + public static BeanFactory beanFactory; + + static { + initLogger(); + + BeanFactoryLocator beanFactoryLocator = ContextSingletonBeanFactoryLocator.getInstance(); + BeanFactoryReference beanFactoryReference = beanFactoryLocator.useBeanFactory("ctx"); + beanFactory = beanFactoryReference.getFactory(); + } + + /** + * The shared instance. + */ + private static InspectIT plugin = new InspectIT(); + + /** + * Initializes the logger. + */ + private static void initLogger() { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + try { + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + context.reset(); + configurator.doConfigure(InspectIT.class.getResourceAsStream("/config/logback.xml")); + } catch (JoranException je) { // NOPMD NOCHK StatusPrinter will handle this NOCHK + } + StatusPrinter.printInCaseOfErrorsOrWarnings(context); + } + + /** + * Returns a service, if one is registered with the bundle context. + * + * @param clazz + * Class of service. + * @param + * Type + * @return Service or null is service is not registered at the moment. + */ + public static E getService(Class clazz) { + return beanFactory.getBean(clazz); + } + + public static InspectIT getDefault() { + return plugin; + } + + /** + * Creates a simple error dialog. + * + * @param message + * The message of the dialog. + * @param throwable + * The exception to display + * @param code + * The code of the error. -1 is a marker that the code has to be added later. + */ + public void createErrorDialog(String message, Throwable throwable, int code) { + System.out.println(message); + throwable.printStackTrace(); + } + + /** + * Creates a simple error dialog without exception. + * + * @param message + * The message of the dialog. + * @param code + * The code of the error. -1 is a marker that the code has to be added later. + */ + public void createErrorDialog(String message, int code) { + System.out.println(message); + } + +} diff --git a/inspectITJMeter/overwrite/org/eclipse/core/runtime/IProgressMonitor.java b/inspectITJMeter/overwrite/org/eclipse/core/runtime/IProgressMonitor.java new file mode 100644 index 000000000..d5d7d74c3 --- /dev/null +++ b/inspectITJMeter/overwrite/org/eclipse/core/runtime/IProgressMonitor.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2000, 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.core.runtime; + +/* + * This class is needed for the overwritten InspectIT class. There are no changes to this file. + * The other approach would be to include all org.eclipse.core stuff, but this would enlarge + * this package a lot. + * + * For the build in Eclipse this class is excluded, as in Eclipse running an application + * does load the eclipse classes and overwritting the Submonitor results in Verifier problems. + */ + +/** + * The IProgressMonitor interface is implemented by objects that monitor the progress + * of an activity; the methods in this interface are invoked by code that performs the activity. + *

+ * All activity is broken down into a linear sequence of tasks against which progress is reported. + * When a task begins, a beginTask(String, int) + * notification is reported, followed by any number and mixture of progress reports ( + * worked()) and subtask notifications (subTask(String)). When the task is + * eventually completed, a done() notification is reported. After the + * done() notification, the progress monitor cannot be reused; i.e., + * beginTask(String, int) cannot be called again after the call to done(). + *

+ *

+ * A request to cancel an operation can be signaled using the setCanceled method. + * Operations taking a progress monitor are expected to poll the monitor (using + * isCanceled) periodically and abort at their earliest convenience. Operation can + * however choose to ignore cancelation requests. + *

+ *

+ * Since notification is synchronous with the activity itself, the listener should provide a fast + * and robust implementation. If the handling of notifications would involve blocking operations, or + * operations which might throw uncaught exceptions, the notifications should be queued, and the + * actual processing deferred (or perhaps delegated to a separate thread). + *

+ *

+ * This interface can be used without OSGi running. + *

+ *

+ * Clients may implement this interface. + *

+ */ +//NOCHKALL +public interface IProgressMonitor { + + /** + * Constant indicating an unknown amount of work. + */ + public final static int UNKNOWN = -1; + + /** + * Notifies that the main task is beginning. This must only be called once on a given progress + * monitor instance. + * + * @param name + * the name (or description) of the main task + * @param totalWork + * the total number of work units into which the main task is been subdivided. If the + * value is UNKNOWN the implementation is free to indicate progress in a + * way which doesn't require the total number of work units in advance. + */ + public void beginTask(String name, int totalWork); + + /** + * Notifies that the work is done; that is, either the main task is completed or the user + * canceled it. This method may be called more than once (implementations should be prepared to + * handle this case). + */ + public void done(); + + /** + * Internal method to handle scaling correctly. This method must not be called by a client. + * Clients should always use the method worked(int). + * + * @param work + * the amount of work done + */ + public void internalWorked(double work); + + /** + * Returns whether cancelation of current operation has been requested. Long-running operations + * should poll to see if cancelation has been requested. + * + * @return true if cancellation has been requested, and false + * otherwise + * @see #setCanceled(boolean) + */ + public boolean isCanceled(); + + /** + * Sets the cancel state to the given value. + * + * @param value + * true indicates that cancelation has been requested (but not + * necessarily acknowledged); false clears this flag + * @see #isCanceled() + */ + public void setCanceled(boolean value); + + /** + * Sets the task name to the given value. This method is used to restore the task label after a + * nested operation was executed. Normally there is no need for clients to call this method. + * + * @param name + * the name (or description) of the main task + * @see #beginTask(java.lang.String, int) + */ + public void setTaskName(String name); + + /** + * Notifies that a subtask of the main task is beginning. Subtasks are optional; the main task + * might not have subtasks. + * + * @param name + * the name (or description) of the subtask + */ + public void subTask(String name); + + /** + * Notifies that a given number of work unit of the main task has been completed. Note that this + * amount represents an installment, as opposed to a cumulative amount of work done to date. + * + * @param work + * a non-negative number of work units just completed + */ + public void worked(int work); +} diff --git a/inspectITJMeter/overwrite/org/eclipse/core/runtime/SubMonitor.java b/inspectITJMeter/overwrite/org/eclipse/core/runtime/SubMonitor.java new file mode 100644 index 000000000..e99cfc68d --- /dev/null +++ b/inspectITJMeter/overwrite/org/eclipse/core/runtime/SubMonitor.java @@ -0,0 +1,374 @@ +package org.eclipse.core.runtime; + +/* + * This class is needed for the overwritten InspectIT class. There are no changes to this file. + * The other approach would be to include all org.eclipse.core stuff, but this would enlarge + * this package a lot. + * + * For the build in Eclipse this class is excluded, as in Eclipse running an application + * does load the eclipse classes and overwritting the Submonitor results in Verifier problems. + */ + +//NOCHKALL +public final class SubMonitor { + + /** + * May be passed as a flag to newChild. Indicates that the calls to subTask on the child should + * be ignored. Without this flag, calling subTask on the child will result in a call to subTask + * on its parent. + */ + public static final int SUPPRESS_SUBTASK = 0x0001; + + /** + * May be passed as a flag to newChild. Indicates that strings passed into beginTask should be + * ignored. If this flag is specified, then the progress monitor instance will accept null as + * the first argument to beginTask. Without this flag, any string passed to beginTask will + * result in a call to setTaskName on the parent. + */ + public static final int SUPPRESS_BEGINTASK = 0x0002; + + /** + * May be passed as a flag to newChild. Indicates that strings passed into setTaskName should be + * ignored. If this string is omitted, then a call to setTaskName on the child will result in a + * call to setTaskName on the parent. + */ + public static final int SUPPRESS_SETTASKNAME = 0x0004; + + /** + * May be passed as a flag to newChild. Indicates that strings passed to setTaskName, subTask, + * and beginTask should all be ignored. + */ + public static final int SUPPRESS_ALL_LABELS = SUPPRESS_SETTASKNAME | SUPPRESS_BEGINTASK | SUPPRESS_SUBTASK; + + /** + * May be passed as a flag to newChild. Indicates that strings passed to setTaskName, subTask, + * and beginTask should all be propagated to the parent. + */ + public static final int SUPPRESS_NONE = 0; + + /** + * Creates a new SubMonitor that will report its progress via the given RootInfo. + * + * @param rootInfo + * the root of this progress monitor tree + * @param totalWork + * total work to perform on the given progress monitor + * @param availableToChildren + * number of ticks allocated for this instance's children + * @param flags + * a bitwise combination of the SUPPRESS_* constants + */ + private SubMonitor() { + } + + /** + *

+ * Converts an unknown (possibly null) IProgressMonitor into a SubMonitor. It is not necessary + * to call done() on the result, but the caller is responsible for calling done() on the + * argument. Calls beginTask on the argument. + *

+ * + *

+ * This method should generally be called at the beginning of a method that accepts an + * IProgressMonitor in order to convert the IProgressMonitor into a SubMonitor. + *

+ * + * @param monitor + * monitor to convert to a SubMonitor instance or null. Treats null as a new instance + * of NullProgressMonitor. + * @return a SubMonitor instance that adapts the argument + */ + public static SubMonitor convert(IProgressMonitor monitor) { + return convert(monitor, "", 0); //$NON-NLS-1$ + } + + /** + *

+ * Converts an unknown (possibly null) IProgressMonitor into a SubMonitor allocated with the + * given number of ticks. It is not necessary to call done() on the result, but the caller is + * responsible for calling done() on the argument. Calls beginTask on the argument. + *

+ * + *

+ * This method should generally be called at the beginning of a method that accepts an + * IProgressMonitor in order to convert the IProgressMonitor into a SubMonitor. + *

+ * + * @param monitor + * monitor to convert to a SubMonitor instance or null. Treats null as a new instance + * of NullProgressMonitor. + * @param work + * number of ticks that will be available in the resulting monitor + * @return a SubMonitor instance that adapts the argument + */ + public static SubMonitor convert(IProgressMonitor monitor, int work) { + return convert(monitor, "", work); //$NON-NLS-1$ + } + + /** + *

+ * Converts an unknown (possibly null) IProgressMonitor into a SubMonitor allocated with the + * given number of ticks. It is not necessary to call done() on the result, but the caller is + * responsible for calling done() on the argument. Calls beginTask on the argument. + *

+ * + *

+ * This method should generally be called at the beginning of a method that accepts an + * IProgressMonitor in order to convert the IProgressMonitor into a SubMonitor. + *

+ * + * @param monitor + * to convert into a SubMonitor instance or null. If given a null argument, the + * resulting SubMonitor will not report its progress anywhere. + * @param taskName + * user readable name to pass to monitor.beginTask. Never null. + * @param work + * initial number of ticks to allocate for children of the SubMonitor + * @return a new SubMonitor instance that is a child of the given monitor + */ + public static SubMonitor convert(IProgressMonitor monitor, String taskName, int work) { + return new SubMonitor(); + } + + /** + *

+ * Sets the work remaining for this SubMonitor instance. This is the total number of ticks that + * may be reported by all subsequent calls to worked(int), newChild(int), etc. This may be + * called many times for the same SubMonitor instance. When this method is called, the remaining + * space on the progress monitor is redistributed into the given number of ticks. + *

+ * + *

+ * It doesn't matter how much progress has already been reported with this SubMonitor instance. + * If you call setWorkRemaining(100), you will be able to report 100 more ticks of work before + * the progress meter reaches 100%. + *

+ * + * @param workRemaining + * total number of remaining ticks + * @return the receiver + */ + public SubMonitor setWorkRemaining(int workRemaining) { + return new SubMonitor(); + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.IProgressMonitor#isCanceled() + */ + public boolean isCanceled() { + return true; + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.IProgressMonitor#setTaskName(java.lang.String) + */ + public void setTaskName(String name) { + } + + /** + * Starts a new main task. The string argument is ignored if and only if the SUPPRESS_BEGINTASK + * flag has been set on this SubMonitor instance. + * + *

+ * This method is equivalent calling setWorkRemaining(...) on the receiver. Unless the + * SUPPRESS_BEGINTASK flag is set, this will also be equivalent to calling setTaskName(...) on + * the parent. + *

+ * + * @param name + * new main task name + * @param totalWork + * number of ticks to allocate + * + * @see org.eclipse.core.runtime.IProgressMonitor#beginTask(java.lang.String, int) + */ + public void beginTask(String name, int totalWork) { + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.IProgressMonitor#done() + */ + public void done() { + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.IProgressMonitor#internalWorked(double) + */ + public void internalWorked(double work) { + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.IProgressMonitor#subTask(java.lang.String) + */ + public void subTask(String name) { + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.IProgressMonitor#worked(int) + */ + public void worked(int work) { + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.IProgressMonitor#setCanceled(boolean) + */ + public void setCanceled(boolean b) { + } + + /** + *

+ * Creates a sub progress monitor that will consume the given number of ticks from the receiver. + * It is not necessary to call beginTask or done on the result. + * However, the resulting progress monitor will not report any work after the first call to + * done() or before ticks are allocated. Ticks may be allocated by calling beginTask or + * setWorkRemaining. + *

+ * + *

+ * Each SubMonitor only has one active child at a time. Each time newChild() is called, the + * result becomes the new active child and any unused progress from the previously-active child + * is consumed. + *

+ * + *

+ * This is property makes it unnecessary to call done() on a SubMonitor instance, since child + * monitors are automatically cleaned up the next time the parent is touched. + *

+ * + *
 
+	 *      ////////////////////////////////////////////////////////////////////////////
+	 *      // Example 1: Typical usage of newChild
+	 *      void myMethod(IProgressMonitor parent) {
+	 *          SubMonitor progress = SubMonitor.convert(parent, 100); 
+	 *          doSomething(progress.newChild(50));
+	 *          doSomethingElse(progress.newChild(50));
+	 *      }
+	 *      
+	 *      ////////////////////////////////////////////////////////////////////////////
+	 *      // Example 2: Demonstrates the function of active children. Creating children
+	 *      // is sufficient to smoothly report progress, even if worked(...) and done()
+	 *      // are never called.
+	 *      void myMethod(IProgressMonitor parent) {
+	 *          SubMonitor progress = SubMonitor.convert(parent, 100);
+	 *          
+	 *          for (int i = 0; i < 100; i++) {
+	 *              // Creating the next child monitor will clean up the previous one,
+	 *              // causing progress to be reported smoothly even if we don't do anything
+	 *              // with the monitors we create
+	 *          	progress.newChild(1);
+	 *          }
+	 *      }
+	 *      
+	 *      ////////////////////////////////////////////////////////////////////////////
+	 *      // Example 3: Demonstrates a common anti-pattern
+	 *      void wrongMethod(IProgressMonitor parent) {
+	 *          SubMonitor progress = SubMonitor.convert(parent, 100);
+	 *          
+	 *          // WRONG WAY: Won't have the intended effect, as only one of these progress
+	 *          // monitors may be active at a time and the other will report no progress.
+	 *          callMethod(progress.newChild(50), computeValue(progress.newChild(50)));
+	 *      }
+	 *      
+	 *      void rightMethod(IProgressMonitor parent) {
+	 *          SubMonitor progress = SubMonitor.convert(parent, 100);
+	 *          
+	 *          // RIGHT WAY: Break up method calls so that only one SubMonitor is in use at a time.
+	 *          Object someValue = computeValue(progress.newChild(50));
+	 *          callMethod(progress.newChild(50), someValue);
+	 *      }
+	 * 
+ * + * @param totalWork + * number of ticks to consume from the receiver + * @return new sub progress monitor that may be used in place of a new SubMonitor + */ + public SubMonitor newChild(int totalWork) { + return newChild(totalWork, SUPPRESS_BEGINTASK); + } + + /** + *

+ * Creates a sub progress monitor that will consume the given number of ticks from the receiver. + * It is not necessary to call beginTask or done on the result. + * However, the resulting progress monitor will not report any work after the first call to + * done() or before ticks are allocated. Ticks may be allocated by calling beginTask or + * setWorkRemaining. + *

+ * + *

+ * Each SubMonitor only has one active child at a time. Each time newChild() is called, the + * result becomes the new active child and any unused progress from the previously-active child + * is consumed. + *

+ * + *

+ * This is property makes it unnecessary to call done() on a SubMonitor instance, since child + * monitors are automatically cleaned up the next time the parent is touched. + *

+ * + *
 
+	 *      ////////////////////////////////////////////////////////////////////////////
+	 *      // Example 1: Typical usage of newChild
+	 *      void myMethod(IProgressMonitor parent) {
+	 *          SubMonitor progress = SubMonitor.convert(parent, 100); 
+	 *          doSomething(progress.newChild(50));
+	 *          doSomethingElse(progress.newChild(50));
+	 *      }
+	 *      
+	 *      ////////////////////////////////////////////////////////////////////////////
+	 *      // Example 2: Demonstrates the function of active children. Creating children
+	 *      // is sufficient to smoothly report progress, even if worked(...) and done()
+	 *      // are never called.
+	 *      void myMethod(IProgressMonitor parent) {
+	 *          SubMonitor progress = SubMonitor.convert(parent, 100);
+	 *          
+	 *          for (int i = 0; i < 100; i++) {
+	 *              // Creating the next child monitor will clean up the previous one,
+	 *              // causing progress to be reported smoothly even if we don't do anything
+	 *              // with the monitors we create
+	 *          	progress.newChild(1);
+	 *          }
+	 *      }
+	 *      
+	 *      ////////////////////////////////////////////////////////////////////////////
+	 *      // Example 3: Demonstrates a common anti-pattern
+	 *      void wrongMethod(IProgressMonitor parent) {
+	 *          SubMonitor progress = SubMonitor.convert(parent, 100);
+	 *          
+	 *          // WRONG WAY: Won't have the intended effect, as only one of these progress
+	 *          // monitors may be active at a time and the other will report no progress.
+	 *          callMethod(progress.newChild(50), computeValue(progress.newChild(50)));
+	 *      }
+	 *      
+	 *      void rightMethod(IProgressMonitor parent) {
+	 *          SubMonitor progress = SubMonitor.convert(parent, 100);
+	 *          
+	 *          // RIGHT WAY: Break up method calls so that only one SubMonitor is in use at a time.
+	 *          Object someValue = computeValue(progress.newChild(50));
+	 *          callMethod(progress.newChild(50), someValue);
+	 *      }
+	 * 
+ * + * @param totalWork + * number of ticks to consume from the receiver + * @return new sub progress monitor that may be used in place of a new SubMonitor + */ + public SubMonitor newChild(int totalWork, int suppressFlags) { + return new SubMonitor(); + } + + protected static boolean eq(Object o1, Object o2) { + if (o1 == null) + return (o2 == null); + if (o2 == null) + return false; + return o1.equals(o2); + } +} diff --git a/inspectITJMeter/resources/ivy/ivy.xml b/inspectITJMeter/resources/ivy/ivy.xml new file mode 100644 index 000000000..afd6b77c5 --- /dev/null +++ b/inspectITJMeter/resources/ivy/ivy.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inspectITJMeter/resources/testng/testng-localtest.xml b/inspectITJMeter/resources/testng/testng-localtest.xml new file mode 100644 index 000000000..b54f9832f --- /dev/null +++ b/inspectITJMeter/resources/testng/testng-localtest.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/inspectITJMeter/resources/testng/testng.xml b/inspectITJMeter/resources/testng/testng.xml new file mode 100644 index 000000000..096aa24a4 --- /dev/null +++ b/inspectITJMeter/resources/testng/testng.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/inspectITJMeter/src/beanRefContext.xml b/inspectITJMeter/src/beanRefContext.xml new file mode 100644 index 000000000..b16233fb3 --- /dev/null +++ b/inspectITJMeter/src/beanRefContext.xml @@ -0,0 +1,17 @@ + + + + + + + + + spring-context-model-main.xml + + + + + diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITAggregatedSQL.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITAggregatedSQL.java new file mode 100644 index 000000000..f1183a666 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITAggregatedSQL.java @@ -0,0 +1,44 @@ +package info.novatec.inspectit.jmeter; + +import info.novatec.inspectit.communication.data.SqlStatementData; +import info.novatec.inspectit.jmeter.data.AggregatedSQLResult; +import info.novatec.inspectit.jmeter.data.ResultBase; + +import java.util.List; + +/** + * Sampler that requests the aggregated SQL view. + * + * @author Stefan Siegl + */ +public class InspectITAggregatedSQL extends InspectITSamplerBase { + + /** query template. */ + private SqlStatementData sqlTemplate; + /** platformId. */ + private Long platformId; + /** Result. */ + private List sqlsResult; + + @Override + public Configuration[] getRequiredConfig() { + return new Configuration[] { Configuration.PLATFORM_ID }; + } + + @Override + public void setup() { + sqlTemplate = new SqlStatementData(); + platformId = getValue(Configuration.PLATFORM_ID); + sqlTemplate.setPlatformIdent(platformId); + } + + @Override + public ResultBase getResult() { + return new AggregatedSQLResult(platformId, sqlsResult.size()); + } + + @Override + public void run() { + sqlsResult = repository.getSqlDataAccessService().getAggregatedSqlStatements(sqlTemplate); + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITAggregatedTimer.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITAggregatedTimer.java new file mode 100644 index 000000000..6b709f9c1 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITAggregatedTimer.java @@ -0,0 +1,43 @@ +package info.novatec.inspectit.jmeter; + +import info.novatec.inspectit.communication.data.TimerData; +import info.novatec.inspectit.jmeter.data.AggregatedTimerResult; +import info.novatec.inspectit.jmeter.data.ResultBase; + +import java.util.List; + +/** + * Sampler that requests the aggregated timer view. + * + * @author Stefan Siegl + */ +public class InspectITAggregatedTimer extends InspectITSamplerBase { + + /** search template. */ + private TimerData timerData; + /** platformId. */ + private Long platformId; + /** result. */ + private List timers; + + public Configuration[] getRequiredConfig() { + return new Configuration[] { Configuration.PLATFORM_ID }; + } + + @Override + public void setup() { + timerData = new TimerData(); + platformId = getValue(Configuration.PLATFORM_ID); + timerData.setPlatformIdent(platformId); + } + + @Override + public void run() { + timers = repository.getTimerDataAccessService().getAggregatedTimerData(timerData); + } + + @Override + public ResultBase getResult() { + return new AggregatedTimerResult(platformId, timers.size()); + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITExceptionResult.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITExceptionResult.java new file mode 100644 index 000000000..027049f89 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITExceptionResult.java @@ -0,0 +1,47 @@ +package info.novatec.inspectit.jmeter; + +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.jmeter.data.ExceptionResult; +import info.novatec.inspectit.jmeter.data.ResultBase; + +import java.util.List; + +/** + * Sampler that requests the exception view. + * + * @author Stefan Siegl + */ +public class InspectITExceptionResult extends InspectITSamplerBase { + + /** search template. */ + private ExceptionSensorData exceptionData; + /** platformId. */ + private Long platformId; + /** sorting. */ + private ResultComparator sorting; + /** result. */ + private List exceptions; + + public Configuration[] getRequiredConfig() { + return new Configuration[] { Configuration.PLATFORM_ID, Configuration.EXCEPTION_SORT }; + } + + @Override + public void setup() { + exceptionData = new ExceptionSensorData(); + platformId = getValue(Configuration.PLATFORM_ID); + exceptionData.setPlatformIdent(platformId); + sorting = getValue(Configuration.EXCEPTION_SORT); + } + + @Override + public void run() { + exceptions = repository.getExceptionDataAccessService().getUngroupedExceptionOverview(exceptionData, sorting); + } + + @Override + public ResultBase getResult() { + return new ExceptionResult(platformId, exceptions.size()); + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITGetConnectedAgents.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITGetConnectedAgents.java new file mode 100644 index 000000000..a106bf4a3 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITGetConnectedAgents.java @@ -0,0 +1,73 @@ +package info.novatec.inspectit.jmeter; + +import info.novatec.inspectit.cmr.model.PlatformIdent; +import info.novatec.inspectit.communication.data.cmr.AgentStatusData; +import info.novatec.inspectit.communication.data.cmr.AgentStatusData.AgentConnection; +import info.novatec.inspectit.jmeter.data.ConnectedAgent; +import info.novatec.inspectit.jmeter.data.ConnectedAgents; +import info.novatec.inspectit.jmeter.data.InspectITResultMarker; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Sampler to get all connected agents. + * + * @author Stefan Siegl + */ +public class InspectITGetConnectedAgents extends InspectITSamplerBase { + + /** Connected agents. */ + private Map agentStatus; + + @Override + public Configuration[] getRequiredConfig() { + return new Configuration[] {}; + } + + @Override + public void run() throws Throwable { + agentStatus = repository.getGlobalDataAccessService().getAgentsOverview(); + } + + @Override + public InspectITResultMarker getResult() { + List agentList = new ArrayList(); + for (Map.Entry entry : agentStatus.entrySet()) { + if (null == entry.getValue()) { + // This nice agent is not connected, thank you service documentation for not + // mentioning that :) + continue; + } + + agentList.add(new ConnectedAgent(entry.getKey(), convertAgentStatus(entry.getValue().getAgentConnection()))); + } + + return new ConnectedAgents(agentList); + } + + /** + * Convert agent status to readable format. + * + * @param status + * the status. + * @return readable format. + */ + private String convertAgentStatus(AgentConnection status) { + if (status.equals(AgentConnection.NEVER_CONNECTED)) { + return "neverConnected"; + } else if (status.equals(AgentConnection.CONNECTED)) { + return "connected"; + } else if (status.equals(AgentConnection.DISCONNECTED)) { + return "disconnected"; + } else { + return "unknown"; + } + } + + @Override + public void setup() { + // not needed + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITHttpAggregation.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITHttpAggregation.java new file mode 100644 index 000000000..456da3b43 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITHttpAggregation.java @@ -0,0 +1,22 @@ +package info.novatec.inspectit.jmeter; + +import info.novatec.inspectit.jmeter.data.HttpAggregatedResult; +import info.novatec.inspectit.jmeter.data.InspectITResultMarker; + +/** + * Sampler that requests the aggregated http view. + * + * @author Stefan Siegl + */ +public class InspectITHttpAggregation extends InspectITHttpAggregationBase { + + @Override + public void run() throws Throwable { + httpResult = repository.getHttpTimerDataAccessService().getAggregatedTimerData(httpTemplate, false); + } + + @Override + public InspectITResultMarker getResult() { + return new HttpAggregatedResult(platformId, httpResult.size()); + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITHttpAggregationBase.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITHttpAggregationBase.java new file mode 100644 index 000000000..95abbbc96 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITHttpAggregationBase.java @@ -0,0 +1,33 @@ +package info.novatec.inspectit.jmeter; + +import info.novatec.inspectit.communication.data.HttpTimerData; + +import java.util.List; + +/** + * Base class for Http related inspectIT operations. + * + * @author Stefan Siegl + * + */ +public abstract class InspectITHttpAggregationBase extends InspectITSamplerBase { + + /** query template. */ + protected HttpTimerData httpTemplate; + /** platformId. */ + protected Long platformId; + /** Result. */ + protected List httpResult; + + @Override + public Configuration[] getRequiredConfig() { + return new Configuration[] { Configuration.PLATFORM_ID }; + } + + @Override + public void setup() throws Exception { + httpTemplate = new HttpTimerData(); + platformId = getValue(Configuration.PLATFORM_ID); + httpTemplate.setPlatformIdent(platformId); + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITHttpUsecaseAggregation.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITHttpUsecaseAggregation.java new file mode 100644 index 000000000..e982bdc54 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITHttpUsecaseAggregation.java @@ -0,0 +1,22 @@ +package info.novatec.inspectit.jmeter; + +import info.novatec.inspectit.jmeter.data.HttpUsecaseResult; +import info.novatec.inspectit.jmeter.data.InspectITResultMarker; + +/** + * Sampler that requests the usecase aggregated http view. + * + * @author Stefan Siegl + */ +public class InspectITHttpUsecaseAggregation extends InspectITHttpAggregationBase { + + @Override + public void run() throws Throwable { + httpResult = repository.getHttpTimerDataAccessService().getTaggedAggregatedTimerData(httpTemplate, false); + } + + @Override + public InspectITResultMarker getResult() { + return new HttpUsecaseResult(platformId, httpResult.size()); + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITInvocationDetails.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITInvocationDetails.java new file mode 100644 index 000000000..bedec89b4 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITInvocationDetails.java @@ -0,0 +1,40 @@ +package info.novatec.inspectit.jmeter; + +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.jmeter.data.InspectITResultMarker; +import info.novatec.inspectit.jmeter.data.InvocationDetailResult; + +/** + * Sampler to get the invocation details. + * + * @author Stefan Siegl. + */ +public class InspectITInvocationDetails extends InspectITSamplerBase { + + /** Search template. */ + private InvocationSequenceData template; + /** Result. */ + private InvocationSequenceData result; + + @Override + public Configuration[] getRequiredConfig() { + return new Configuration[] { Configuration.INVOC_ID }; + } + + @Override + public void run() { + result = repository.getInvocationDataAccessService().getInvocationSequenceDetail(template); + } + + @Override + public InspectITResultMarker getResult() { + return new InvocationDetailResult(result); + } + + @Override + public void setup() { + template = new InvocationSequenceData(); + Long invocId = getValue(Configuration.INVOC_ID); + template.setId(invocId); + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITInvocationOverview.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITInvocationOverview.java new file mode 100644 index 000000000..4789b759d --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITInvocationOverview.java @@ -0,0 +1,59 @@ +package info.novatec.inspectit.jmeter; + +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.communication.data.InvocationSequenceData; +import info.novatec.inspectit.jmeter.data.InspectITResultMarker; +import info.novatec.inspectit.jmeter.data.InvocationOverviewResult; + +import java.util.ArrayList; +import java.util.List; + +/** + * Sampler to get the invocation sequence overview. + * + * @author Stefan Siegl + */ +public class InspectITInvocationOverview extends InspectITSamplerBase { + + /** platformId. */ + private Long platformIdent; + /** Amount of invocation sequences to get. */ + private Integer count; + /** Integrate the invocation IDs in the result. */ + private Boolean detailedInvocationOutput; + /** result. */ + List invocs; + /** sorting. */ + private ResultComparator sorting; + + @Override + public Configuration[] getRequiredConfig() { + return new Configuration[] { Configuration.PLATFORM_ID, Configuration.INVOC_COUNT, Configuration.INVOCATION_OVERVIEW_SORT, Configuration.OUTPUT }; + } + + @Override + public void setup() { + platformIdent = getValue(Configuration.PLATFORM_ID); + count = getValue(Configuration.INVOC_COUNT); + detailedInvocationOutput = getValue(Configuration.OUTPUT); + sorting = getValue(Configuration.INVOCATION_OVERVIEW_SORT); + } + + @Override + public void run() { + invocs = repository.getInvocationDataAccessService().getInvocationSequenceOverview(platformIdent, count, sorting); + } + + @Override + public InspectITResultMarker getResult() { + List invocationIds = null; + if (detailedInvocationOutput) { + invocationIds = new ArrayList(count); + for (InvocationSequenceData invocationSequenceData : invocs) { + invocationIds.add(invocationSequenceData.getId()); + } + } + return new InvocationOverviewResult(platformIdent, count, invocationIds); + } + +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITSamplerBase.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITSamplerBase.java new file mode 100644 index 000000000..06c755e47 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/InspectITSamplerBase.java @@ -0,0 +1,262 @@ +package info.novatec.inspectit.jmeter; + +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.jmeter.data.InspectITResultMarker; +import info.novatec.inspectit.jmeter.util.ObjectConverter; +import info.novatec.inspectit.jmeter.util.ResultService; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition.OnlineStatus; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.java.sampler.JavaSamplerClient; +import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext; +import org.apache.jmeter.samplers.SampleResult; + +/** + * Base class of an inspectIT JMeter sampler. This class is responsible for creating the connection + * to the CMR server and for providing base services that all samplers will need. + * + * This class also provides a list of all configuration elements that can be used for the samplers. + * The sampler is to realize the method getRequiredConfig and return all configuration + * elements that it needs. The base class will deal with providing the input in jmeter for these + * configurations and allow to get the concrete value during the execution. + * + * Note: In JMeter each threads gets its own instances of the sampler, thus internal synchronisation + * is not necessary. + * + * @author Stefan Siegl + */ +public abstract class InspectITSamplerBase implements JavaSamplerClient { + + /** + * The CMR repository that is accessed. + */ + CmrRepositoryDefinition repository; + + /** + * The JMeter context of this test run. The use of this context is usually not needed as all + * configuration elements can be easier read using the service methods of this base class. + */ + private JavaSamplerContext context; + + /** + * Available configuration settings that the sampler can use. + */ + public enum Configuration { + /** The host of the CMR repository. */ + HOST("HOST", "${HOST}", String.class), + /** The port of the CMR repository. */ + PORT("PORT", "${PORT}", Integer.class), + /** The platformId of the agent the action will be performed on. */ + PLATFORM_ID("PLATFORM_IDENT", "${P_ID}", Long.class), + /** The number of elements that should be requested. */ + INVOC_COUNT("COUNT", "-1", Integer.class), + /** The ID of the invocation sequence. */ + INVOC_ID("INVOC_ID", "${INVOC_ID}", Long.class), + /** Whether or not extended output should be provided. */ + OUTPUT("OUTPUT", "false", Boolean.class), + /** Sorting of the invocation overview. */ + INVOCATION_OVERVIEW_SORT("SORT BY [TIMESTAMP|CHILD_COUNT|DURATION|NESTED_DATA|URI|USE_CASE]", "TIMESTAMP", ResultComparator.class), + /** Sorting of the exception overview. */ + EXCEPTION_SORT("SORT BY [TIMESTAMP|FQN|ERROR_MESSAGE]", "TIMESTAMP", ResultComparator.class); + + /** + * the name of the configuration element. This will be displayed 1:1 in jmeter. + */ + public String name; // NOCHK + /** the default value of the configuration. */ + public String defaultValue; // NOCHK + /** the type of the configuration. */ + public Class type; // NOCHK + + /** + * Creates a new instance of the configuration. + * + * @param name + * the name. + * @param defaultValue + * the default value. + * @param type + * the type. + */ + private Configuration(String name, String defaultValue, Class type) { + this.name = name; + this.defaultValue = defaultValue; + this.type = type; + } + } + + @Override + public final void setupTest(JavaSamplerContext context) { + this.context = context; + if (repository == null) { + String host = context.getParameter(Configuration.HOST.name); + String port = context.getParameter(Configuration.PORT.name); + repository = new CmrRepositoryDefinition(host, Integer.parseInt(port)); + } + + repository.refreshOnlineStatus(); + if (repository.getOnlineStatus() != OnlineStatus.ONLINE) { + throw new RuntimeException("Server is offline."); // NOPMD + } + } + + @Override + public final SampleResult runTest(JavaSamplerContext context) { + ResultService resultService = ResultService.newInstance(); + this.context = context; + + try { + checkContext(context); + } catch (ConfigurationNotExistException e) { + resultService.start(); + return resultService.fail(e); + } + + try { + setup(); + } catch (Exception e) { + resultService.start(); + return resultService.fail(e); + } + + resultService.start(); + try { + run(); + resultService.success(); + resultService.setResult(getResult()); + } catch (Throwable e) { // NOPMD + return resultService.fail(e); + } + + return resultService.getResult(); + } + + /** + * Checks if the required configurations are in fact passed as JMeter parameters. + * + * @param context + * the jmeter context. + * @throws ConfigurationNotExistException + * if the context does not provide a required parameter. + */ + private void checkContext(JavaSamplerContext context) throws ConfigurationNotExistException { + List necessaryConfigurations; + Configuration[] requiredConfigs = getRequiredConfig(); + if (null == requiredConfigs) { // no configs required + necessaryConfigurations = new ArrayList(); + } else { + necessaryConfigurations = new ArrayList(Arrays.asList(requiredConfigs)); + } + necessaryConfigurations.add(Configuration.HOST); + necessaryConfigurations.add(Configuration.PORT); + + for (Configuration configuration : necessaryConfigurations) { + if (!context.containsParameter(configuration.name)) { + throw new ConfigurationNotExistException("Context does not contain the required key " + configuration.name + "!"); + } + } + } + + /** + * Provide all configuration elements that this sampler needs. Please be aware that the default + * configuration elements {@link Configuration}.HOST and {@link Configuration}.PORT all always + * integrated by default. + * + * @return all {@code CONFIG} elements that this sampler needs. + */ + public abstract Configuration[] getRequiredConfig(); + + /** + * Execute the test itself. + * + * If an exception is raised, the test run is marked as an error, otherwise the test is expected + * to be ok. If you need additional functionality you need to override the method + * {@code runTest} and do all formatting and operations on the {@code SampleResult} by yourself. + * + * @throws Throwable + * if the test cannot be run. + */ + public abstract void run() throws Throwable; + + /** + * The test is expected to return an object representation of the result. This service will + * convert this representation to XML to make it easier to display. + * + * @return the test results as object. Returning null means that nothing should be + * displayed. + */ + public abstract InspectITResultMarker getResult(); + + /** + * Called before the test run - use it to initialize everything you need. Yes you could also use + * the setupTest method, but this one is jmeter related so you could read the + * context yourself which you do not need as you can use the Configuration class. + * + * @throws Exception + * in case of initialization error. + */ + public abstract void setup() throws Exception; + + @Override + public final void teardownTest(JavaSamplerContext arg0) { // NOPMD + // not needed + } + + @Override + public final Arguments getDefaultParameters() { + Arguments arguments = new Arguments(); + arguments.addArgument(new Argument(Configuration.HOST.name, Configuration.HOST.defaultValue)); + arguments.addArgument(new Argument(Configuration.PORT.name, Configuration.PORT.defaultValue)); + + if (null != getRequiredConfig()) { + for (Configuration config : getRequiredConfig()) { + arguments.addArgument(new Argument(config.name, config.defaultValue)); + } + } + + return arguments; + } + + /** + * Reads the given {@code Configuration} from the context that the jmeter testcase provided. + * + * @param e + * the {@code Configuration} to read. + * @param + * the type of the {@code Configuration}. + * @return the value of the {@code Configuration} + */ + @SuppressWarnings("unchecked") + public T getValue(Configuration e) { + T res = (T) e.type.cast(ObjectConverter.convert(context.getParameter(e.name), e.type)); + if (null == res) { + throw new RuntimeException("null value when looking up " + e.name); // NOPMD + } + return res; + } + + /** + * Exception thrown if a required configuration is not provided. + * + * @author Stefan Siegl. + */ + @SuppressWarnings("serial") + static class ConfigurationNotExistException extends Exception { + + /** + * Constructor. + * + * @param string + * exception message. + */ + public ConfigurationNotExistException(String string) { + super(string); + } + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/AggregatedSQLResult.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/AggregatedSQLResult.java new file mode 100644 index 000000000..e3c3bf11a --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/AggregatedSQLResult.java @@ -0,0 +1,8 @@ +package info.novatec.inspectit.jmeter.data; + +// NOCHKALL +public class AggregatedSQLResult extends CountOnlyResult { + public AggregatedSQLResult(long platformId, int count) { + super(platformId, count); + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/AggregatedTimerResult.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/AggregatedTimerResult.java new file mode 100644 index 000000000..fb10821bb --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/AggregatedTimerResult.java @@ -0,0 +1,8 @@ +package info.novatec.inspectit.jmeter.data; + +// NOCHKALL +public class AggregatedTimerResult extends CountOnlyResult { + public AggregatedTimerResult(long platformId, int count) { + super(platformId, count); + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/ConnectedAgent.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/ConnectedAgent.java new file mode 100644 index 000000000..c3e3efc57 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/ConnectedAgent.java @@ -0,0 +1,16 @@ +package info.novatec.inspectit.jmeter.data; + +import info.novatec.inspectit.cmr.model.PlatformIdent; + +// NOCHKALL +public class ConnectedAgent extends ResultBase { + + public String name; + public String status; + + public ConnectedAgent(PlatformIdent platformId, String status) { + super(platformId.getId()); + this.name = platformId.getAgentName(); + this.status = status; + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/ConnectedAgents.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/ConnectedAgents.java new file mode 100644 index 000000000..893aec479 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/ConnectedAgents.java @@ -0,0 +1,24 @@ +package info.novatec.inspectit.jmeter.data; + +import java.util.List; + +// NOCHKALL +public class ConnectedAgents implements InspectITResultMarker { + + private List connectedAgents; // NOPMD: Cannot change as tests already depend on + // that. + + public ConnectedAgents(List agents) { + super(); + this.connectedAgents = agents; + } + + public List getAgents() { + return connectedAgents; + } + + public void setAgents(List agents) { + this.connectedAgents = agents; + } + +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/CountOnlyResult.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/CountOnlyResult.java new file mode 100644 index 000000000..1eeb13fd0 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/CountOnlyResult.java @@ -0,0 +1,21 @@ +package info.novatec.inspectit.jmeter.data; + +// NOCHKALL +public abstract class CountOnlyResult extends ResultBase { + + protected int count; + + public CountOnlyResult(long platformId, int count) { + super(platformId); + this.count = count; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/ExceptionResult.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/ExceptionResult.java new file mode 100644 index 000000000..3c6d215f8 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/ExceptionResult.java @@ -0,0 +1,9 @@ +package info.novatec.inspectit.jmeter.data; + +// NOCHKALL +public class ExceptionResult extends AggregatedTimerResult { + + public ExceptionResult(long platformId, int count) { + super(platformId, count); + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/HttpAggregatedResult.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/HttpAggregatedResult.java new file mode 100644 index 000000000..5047e9e00 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/HttpAggregatedResult.java @@ -0,0 +1,8 @@ +package info.novatec.inspectit.jmeter.data; + +// NOCHKALL +public class HttpAggregatedResult extends CountOnlyResult { + public HttpAggregatedResult(long platformId, int count) { + super(platformId, count); + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/HttpUsecaseResult.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/HttpUsecaseResult.java new file mode 100644 index 000000000..68c982769 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/HttpUsecaseResult.java @@ -0,0 +1,8 @@ +package info.novatec.inspectit.jmeter.data; + +// NOCHKALL +public class HttpUsecaseResult extends CountOnlyResult { + public HttpUsecaseResult(long platformId, int count) { + super(platformId, count); + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/InspectITResultMarker.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/InspectITResultMarker.java new file mode 100644 index 000000000..2083ea86d --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/InspectITResultMarker.java @@ -0,0 +1,10 @@ +package info.novatec.inspectit.jmeter.data; + +/** + * Marks an object to be the result of a test run. + * + * @author Stefan Siegl + */ +public interface InspectITResultMarker { + +} \ No newline at end of file diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/InvocationDetailResult.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/InvocationDetailResult.java new file mode 100644 index 000000000..b3f37d528 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/InvocationDetailResult.java @@ -0,0 +1,26 @@ +package info.novatec.inspectit.jmeter.data; + +import info.novatec.inspectit.communication.data.InvocationSequenceData; + +// NOCHKALL +public class InvocationDetailResult implements InspectITResultMarker { + long id; + double duration; + long childCount; + + public InvocationDetailResult(long id, double duration, long childCount) { + super(); + this.id = id; + this.duration = duration; + this.childCount = childCount; + } + + public InvocationDetailResult(InvocationSequenceData data) { + super(); + if (null != data) { + this.id = data.getId(); + this.duration = data.getDuration(); + this.childCount = data.getChildCount(); + } + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/InvocationOverviewResult.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/InvocationOverviewResult.java new file mode 100644 index 000000000..2120cce33 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/InvocationOverviewResult.java @@ -0,0 +1,15 @@ +package info.novatec.inspectit.jmeter.data; + +import java.util.List; + +// NOCHKALL +public class InvocationOverviewResult extends ResultBase { + public long count; + public List invocationIds; + + public InvocationOverviewResult(long platformId, long count, List invocationIds) { + super(platformId); + this.count = count; + this.invocationIds = invocationIds; + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/ResultBase.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/ResultBase.java new file mode 100644 index 000000000..5ce61c9a5 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/data/ResultBase.java @@ -0,0 +1,20 @@ +package info.novatec.inspectit.jmeter.data; + +// NOCHKALL +public class ResultBase implements InspectITResultMarker { + + private long platformId; + + public ResultBase(long platformId) { + super(); + this.platformId = platformId; + } + + public long getPlatformId() { + return platformId; + } + + public void setPlatformId(long platformId) { + this.platformId = platformId; + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/util/ObjectConverter.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/util/ObjectConverter.java new file mode 100644 index 000000000..4fddb8e45 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/util/ObjectConverter.java @@ -0,0 +1,266 @@ +package info.novatec.inspectit.jmeter.util; + +/* + * net/balusc/util/ObjectConverter.java Copyright (C) 2007 BalusC This program is free software: you + * can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the License, or (at your option) + * any later version. This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU Lesser General Public License for more details. You should have received a + * copy of the GNU Lesser General Public License along with this library. If not, see + * . Found at + * http://balusc.blogspot.de/2007/08/generic-object-converter.html + */ + +import info.novatec.inspectit.communication.comparator.DefaultDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.ExceptionSensorDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.InvocationSequenceDataComparatorEnum; +import info.novatec.inspectit.communication.comparator.ResultComparator; +import info.novatec.inspectit.communication.data.ExceptionSensorData; +import info.novatec.inspectit.communication.data.InvocationSequenceData; + +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; + +/** + * Generic object converter. + *

+ *

Use examples

+ * + *
+ * Object o1 = Boolean.TRUE;
+ * Integer i = ObjectConverter.convert(o1, Integer.class);
+ * System.out.println(i); // 1
+ * 
+ * Object o2 = "false";
+ * Boolean b = ObjectConverter.convert(o2, Boolean.class);
+ * System.out.println(b); // false
+ * 
+ * Object o3 = new Integer(123);
+ * String s = ObjectConverter.convert(o3, String.class);
+ * System.out.println(s); // 123
+ * 
+ * + * Not all possible conversions are implemented. You can extend the ObjectConverter easily + * by just adding a new method to it, with the appropriate logic. For example: + * + *
+ * public static ToObject fromObjectToObject(FromObject fromObject) {
+ * 	// Implement.
+ * }
+ * 
+ * + * The method name doesn't matter. It's all about the parameter type and the return type. + * + * @author BalusC + * @link http://balusc.blogspot.com/2007/08/generic-object-converter.html + */ +public final class ObjectConverter { + + // Init --------------------------------------------------------------------------------------- + + /** contains all converters. */ + private static final Map CONVERTERS = new HashMap(); + + static { + // Preload converters. + Method[] methods = ObjectConverter.class.getDeclaredMethods(); + for (Method method : methods) { + if (method.getParameterTypes().length == 1) { + // Converter should accept 1 argument. This skips the convert() method. + CONVERTERS.put(method.getParameterTypes()[0].getName() + "_" + method.getReturnType().getName(), method); + } + } + } + + /** Hide. */ + private ObjectConverter() { + // Utility class, hide the constructor. + } + + // Action ------------------------------------------------------------------------------------- + + /** + * Convert the given object value to the given class. + * + * @param from + * The object value to be converted. + * @param to + * The type class which the given object should be converted to. + * @param + * The expected type. + * @return The converted object value. + * @throws NullPointerException + * If 'to' is null. + * @throws UnsupportedOperationException + * If no suitable converter can be found. + * @throws RuntimeException + * If conversion failed somehow. This can be caused by at least an + * ExceptionInInitializerError, IllegalAccessException or InvocationTargetException. + */ + public static T convert(Object from, Class to) throws UnsupportedOperationException, RuntimeException, NullPointerException { + + // Null is just null. + if (from == null) { + return null; + } + + // Can we cast? Then just do it. + if (to.isAssignableFrom(from.getClass())) { + return to.cast(from); + } + + // Lookup the suitable converter. + String converterId = from.getClass().getName() + "_" + to.getName(); + Method converter = CONVERTERS.get(converterId); + if (converter == null) { + throw new UnsupportedOperationException("Cannot convert from " + from.getClass().getName() + " to " + to.getName() + ". Requested converter does not exist."); + } + + // Convert the value. + try { + return to.cast(converter.invoke(to, from)); + } catch (Exception e) { + throw new RuntimeException("Cannot convert from " + from.getClass().getName() + " to " + to.getName() + ". Object to convert was " + from.toString() + ". Conversion failed with " + + e.getMessage(), e); // NOPMD + } + } + + // Converters --------------------------------------------------------------------------------- + + /** + * Converts Integer to Boolean. If integer value is 0, then return FALSE, else return TRUE. + * + * @param value + * The Integer to be converted. + * @return The converted Boolean value. + */ + public static Boolean integerToBoolean(Integer value) { + return value.intValue() == 0 ? Boolean.FALSE : Boolean.TRUE; + } + + /** + * Converts Boolean to Integer. If boolean value is TRUE, then return 1, else return 0. + * + * @param value + * The Boolean to be converted. + * @return The converted Integer value. + */ + public static Integer booleanToInteger(Boolean value) { + return value.booleanValue() ? Integer.valueOf(1) : Integer.valueOf(0); + } + + /** + * Converts Double to BigDecimal. + * + * @param value + * The Double to be converted. + * @return The converted BigDecimal value. + */ + public static BigDecimal doubleToBigDecimal(Double value) { + return new BigDecimal(value.doubleValue()); + } + + /** + * Converts BigDecimal to Double. + * + * @param value + * The BigDecimal to be converted. + * @return The converted Double value. + */ + public static Double bigDecimalToDouble(BigDecimal value) { + return new Double(value.doubleValue()); + } + + /** + * Converts Integer to String. + * + * @param value + * The Integer to be converted. + * @return The converted String value. + */ + public static String integerToString(Integer value) { + return value.toString(); + } + + /** + * Converts String to Integer. + * + * @param value + * The String to be converted. + * @return The converted Integer value. + */ + public static Integer stringToInteger(String value) { + return Integer.valueOf(value); + } + + /** + * Converts Boolean to String. + * + * @param value + * The Boolean to be converted. + * @return The converted String value. + */ + public static String booleanToString(Boolean value) { + return value.toString(); + } + + /** + * Converts String to Boolean. + * + * @param value + * The String to be converted. + * @return The converted Boolean value. + */ + public static Boolean stringToBoolean(String value) { + return Boolean.valueOf(value); + } + + /** + * Converts String to Long. + * + * @param value + * The String to be converted. + * @return The converted Long value. + */ + public static Long stringToLong(String value) { + return Long.valueOf(value); + } + + /** + * Converts String to InvocationSequenceDataComparatorEnum. + * + * @param value + * The String to be converted. + * @return The converted Long value. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static ResultComparator stringToResultComparator(String value) { + // basically there are two enums that the user could specify + + try { + return new ResultComparator(DefaultDataComparatorEnum.valueOf(value)); + } catch (IllegalArgumentException iae) { // NOCHK NOPMD + // the sorting argument is not defined in DefaultDataComparatorEnum, but we can try the + // other enum + } + + try { + return new ResultComparator(ExceptionSensorDataComparatorEnum.valueOf(value)); + } catch (IllegalArgumentException iae) { // NOCHK NOPMD + // the sorting argument is not defined in DefaultDataComparatorEnum, but we can try the + // other enum + } + + try { + return new ResultComparator(InvocationSequenceDataComparatorEnum.valueOf(value)); + } catch (IllegalArgumentException iae) { + throw new RuntimeException("Cannot create InvocationSequenceDataComparatorEnum from input value " + value); // NOPMD + } + } + + // You can implement more converter methods here. + +} \ No newline at end of file diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/util/ResultService.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/util/ResultService.java new file mode 100644 index 000000000..7cd6bde91 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/util/ResultService.java @@ -0,0 +1,98 @@ +package info.novatec.inspectit.jmeter.util; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.apache.jmeter.samplers.SampleResult; + +import com.thoughtworks.xstream.XStream; + +/** + * Provides an easier interface to work with {@code SampleResult} objects and allows to integrate + * Objects as response code. This class will transform these objects to xml. + * + * @author Stefan Siegl + */ +public final class ResultService { + + /** The SampleResult that will be returned. */ + private SampleResult result = new SampleResult(); + + /** The XStream instance. */ + XStream xStream = XStreamFactory.getXStream(); + + /** + * Constructor. + */ + private ResultService() { + } + + /** + * Factory. + * + * @return new Instance. + */ + public static ResultService newInstance() { + return new ResultService(); + } + + /** + * Start the measurement. + */ + public void start() { + result.sampleStart(); + } + + /** + * stop the measurement and set the sample to be successful. + */ + public void success() { + result.sampleEnd(); + result.setSuccessful(true); + } + + /** + * Stops the measurement and fails the test run. + * + * @param e + * the Exception that made this run fail. + * @return the SampleResult + */ + @SuppressWarnings("deprecation") + public SampleResult fail(Throwable e) { + result.sampleEnd(); + result.setSuccessful(false); + result.setResponseMessage(e.getMessage()); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + sw.toString(); // stack trace as a string + result.setResponseData(sw.toString()); + return result; + } + + /** + * Uses the given Object as result of this test run. This object is converted to a + * XML representation. + * + * @param result + * the result of the test run execution. + */ + public void setResult(Object result) { + if (null == result) { + this.result.setResponseData("no response data", "UTF-8"); + } else { + this.result.setResponseData(xStream.toXML(result), "UTF-8"); + } + } + + /** + * Returns the result. + * + * @return the result. + */ + public SampleResult getResult() { + return result; + } +} diff --git a/inspectITJMeter/src/info/novatec/inspectit/jmeter/util/XStreamFactory.java b/inspectITJMeter/src/info/novatec/inspectit/jmeter/util/XStreamFactory.java new file mode 100644 index 000000000..5d4a68b78 --- /dev/null +++ b/inspectITJMeter/src/info/novatec/inspectit/jmeter/util/XStreamFactory.java @@ -0,0 +1,61 @@ +package info.novatec.inspectit.jmeter.util; + +import info.novatec.inspectit.jmeter.data.AggregatedSQLResult; +import info.novatec.inspectit.jmeter.data.AggregatedTimerResult; +import info.novatec.inspectit.jmeter.data.ConnectedAgent; +import info.novatec.inspectit.jmeter.data.ConnectedAgents; +import info.novatec.inspectit.jmeter.data.ExceptionResult; +import info.novatec.inspectit.jmeter.data.HttpAggregatedResult; +import info.novatec.inspectit.jmeter.data.HttpUsecaseResult; +import info.novatec.inspectit.jmeter.data.InvocationDetailResult; +import info.novatec.inspectit.jmeter.data.InvocationOverviewResult; +import info.novatec.inspectit.jmeter.data.ResultBase; + +import com.thoughtworks.xstream.XStream; + +/** + * Factory to create our XStream serializer. + * + * @author Stefan Siegl + */ +public final class XStreamFactory { + + /** + * Hidden constructor. + */ + private XStreamFactory() { + } + + /** + * Returns the XStream serializer. + * + * @return the XStream serializer. + */ + public static XStream getXStream() { + + XStream xStream = new XStream(); + xStream.useAttributeFor(String.class); + xStream.useAttributeFor(Long.class); + xStream.useAttributeFor(Integer.class); + xStream.useAttributeFor(Boolean.class); + xStream.useAttributeFor(Integer.class); + + // strange enough, useAttributeFor(Long.class) only works for the attributes defined in the + // same! class + // but here platformId is inherited. + xStream.useAttributeFor(ResultBase.class, "platformId"); + + xStream.alias("sqls", AggregatedSQLResult.class); + xStream.alias("timers", AggregatedTimerResult.class); + xStream.alias("agents", ConnectedAgents.class); + xStream.alias("agent", ConnectedAgent.class); + xStream.alias("invocation-detail", InvocationDetailResult.class); + xStream.alias("invocations", InvocationOverviewResult.class); + xStream.alias("exceptions", ExceptionResult.class); + xStream.alias("http-usecase", HttpUsecaseResult.class); + xStream.alias("http-aggregation", HttpAggregatedResult.class); + + return xStream; + } + +} diff --git a/inspectITJMeter/test/info/novatec/inspectit/jmeter/SamplerBaseTest.java b/inspectITJMeter/test/info/novatec/inspectit/jmeter/SamplerBaseTest.java new file mode 100644 index 000000000..666ae325f --- /dev/null +++ b/inspectITJMeter/test/info/novatec/inspectit/jmeter/SamplerBaseTest.java @@ -0,0 +1,295 @@ +package info.novatec.inspectit.jmeter; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import info.novatec.inspectit.cmr.service.IGlobalDataAccessService; +import info.novatec.inspectit.jmeter.InspectITSamplerBase.Configuration; +import info.novatec.inspectit.jmeter.data.InspectITResultMarker; +import info.novatec.inspectit.rcp.repository.CmrRepositoryDefinition; + +import java.lang.reflect.Field; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext; +import org.apache.jmeter.samplers.SampleResult; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +@SuppressWarnings("PMD") +// NOCHKALL +public class SamplerBaseTest { + + @Mock + CmrRepositoryDefinition repository; + + @Mock + IGlobalDataAccessService globalDataAccessService; + + @BeforeMethod + public void init() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void hostPortParameterIsMissing() { + InspectITSamplerBase sampler = new InspectITSamplerBase() { + public Configuration[] getRequiredConfig() { + return null; + } + + public void run() throws Throwable { + } + + public InspectITResultMarker getResult() { + return null; + } + + public void setup() throws Exception { + } + }; + + Arguments arguments = new Arguments(); + arguments.addArgument(new Argument(Configuration.HOST.name, "172.16.145.234")); + JavaSamplerContext context = new JavaSamplerContext(arguments); + + // sampler.setup may not be called as this one initializes the CMR Repository. + SampleResult result = sampler.runTest(context); + + assertThat(result.isSuccessful(), is(false)); + assertThat(result.getResponseMessage(), containsString("Context does not contain the required key " + Configuration.PORT.name)); + } + + @Test + public void samplerParameterisMissing() { + InspectITSamplerBase sampler = new InspectITSamplerBase() { + public Configuration[] getRequiredConfig() { + return new Configuration[] { Configuration.INVOC_COUNT }; + } + + public void run() throws Throwable { + } + + public InspectITResultMarker getResult() { + return null; + } + + public void setup() throws Exception { + } + }; + + Arguments arguments = new Arguments(); + arguments.addArgument(new Argument(Configuration.HOST.name, "172.16.145.234")); + arguments.addArgument(new Argument(Configuration.PORT.name, "8182")); + JavaSamplerContext context = new JavaSamplerContext(arguments); + + // sampler.setup may not be called as this one initializes the CMR Repository. + SampleResult result = sampler.runTest(context); + + assertThat(result.isSuccessful(), is(false)); + assertThat(result.getResponseMessage(), containsString("Context does not contain the required key " + Configuration.INVOC_COUNT.name)); + } + + @Test + public void samplerSetupThrowsException() { + final String exceptionMessage = "MYEXCEPTION"; + + InspectITSamplerBase sampler = new InspectITSamplerBase() { + public Configuration[] getRequiredConfig() { + return null; + } + + public void run() throws Throwable { + } + + public InspectITResultMarker getResult() { + return null; + } + + public void setup() throws Exception { + throw new RuntimeException(exceptionMessage); + } + }; + + Arguments arguments = new Arguments(); + arguments.addArgument(new Argument(Configuration.HOST.name, "172.16.145.234")); + arguments.addArgument(new Argument(Configuration.PORT.name, "8182")); + JavaSamplerContext context = new JavaSamplerContext(arguments); + + // sampler.setup may not be called as this one initializes the CMR Repository. + SampleResult result = sampler.runTest(context); + + assertThat(result.isSuccessful(), is(false)); + assertThat(result.getResponseMessage(), containsString(exceptionMessage)); + } + + @Test + public void samplerRunThrowsException() { + final String exceptionMessage = "MYEXCEPTION"; + + InspectITSamplerBase sampler = new InspectITSamplerBase() { + public Configuration[] getRequiredConfig() { + return null; + } + + public void run() throws Throwable { + throw new RuntimeException(exceptionMessage); + } + + public InspectITResultMarker getResult() { + return null; + } + + public void setup() throws Exception { + } + }; + + Arguments arguments = new Arguments(); + arguments.addArgument(new Argument(Configuration.HOST.name, "172.16.145.234")); + arguments.addArgument(new Argument(Configuration.PORT.name, "8182")); + JavaSamplerContext context = new JavaSamplerContext(arguments); + + // sampler.setup may not be called as this one initializes the CMR Repository. + SampleResult result = sampler.runTest(context); + + assertThat(result.isSuccessful(), is(false)); + assertThat(result.getResponseMessage(), containsString(exceptionMessage)); + } + + @Test + public void samplerRun() { + final InspectITResultMarker inspectITResult = new InspectITResultMarker() { + }; + + InspectITSamplerBase sampler = new InspectITSamplerBase() { + public Configuration[] getRequiredConfig() { + return null; + } + + public void run() throws Throwable { + } + + public InspectITResultMarker getResult() { + return inspectITResult; + } + + public void setup() throws Exception { + } + }; + + Arguments arguments = new Arguments(); + arguments.addArgument(new Argument(Configuration.HOST.name, "172.16.145.234")); + arguments.addArgument(new Argument(Configuration.PORT.name, "8182")); + JavaSamplerContext context = new JavaSamplerContext(arguments); + + // sampler.setup may not be called as this one initializes the CMR Repository. + SampleResult result = sampler.runTest(context); + + assertThat(result.isSuccessful(), is(true)); + } + + @Test + public void readConfiguration() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + final InspectITResultMarker inspectITResult = new InspectITResultMarker() { + }; + + InspectITSamplerBase sampler = new InspectITSamplerBase() { + public Configuration[] getRequiredConfig() { + return null; + } + + public void run() throws Throwable { + } + + public InspectITResultMarker getResult() { + return inspectITResult; + } + + public void setup() throws Exception { + } + }; + + final String host = "172.16.145.234"; + final String invocID = "2000"; + + Arguments arguments = new Arguments(); + arguments.addArgument(new Argument(Configuration.HOST.name, host)); + arguments.addArgument(new Argument(Configuration.PORT.name, "8182")); + arguments.addArgument(new Argument(Configuration.INVOC_ID.name, invocID)); + JavaSamplerContext context = new JavaSamplerContext(arguments); + + setSamplerContextInSampler(sampler, context); + + String readHost = sampler.getValue(Configuration.HOST); + Long readInvocId = sampler.getValue(Configuration.INVOC_ID); + + assertThat(readHost, is(equalTo(host))); + assertThat(readInvocId, is(equalTo(Long.valueOf(invocID)))); + } + + @Test(expectedExceptions = RuntimeException.class) + public void readConfigurationNotThere() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + final InspectITResultMarker inspectITResult = new InspectITResultMarker() { + }; + + InspectITSamplerBase sampler = new InspectITSamplerBase() { + public Configuration[] getRequiredConfig() { + return null; + } + + public void run() throws Throwable { + } + + public InspectITResultMarker getResult() { + return inspectITResult; + } + + public void setup() throws Exception { + } + }; + + final String host = "172.16.145.234"; + + Arguments arguments = new Arguments(); + arguments.addArgument(new Argument(Configuration.HOST.name, host)); + arguments.addArgument(new Argument(Configuration.PORT.name, "8182")); + JavaSamplerContext context = new JavaSamplerContext(arguments); + + setSamplerContextInSampler(sampler, context); + + @SuppressWarnings("unused") + Long readInvocId = sampler.getValue(Configuration.INVOC_ID); + } + + /** + * Sets the JavaSamplerContext in the Sampler class. + * + * We do not want to make this field package access, as we want to hide it from the realization + * classes. + * + * @param base + * the sampler base. + * @param context + * the context to write. + * @return the adapted sampler base. + * @throws NoSuchFieldException + * in case of an error. + * @throws SecurityException + * in case of an error. + * @throws IllegalArgumentException + * in case of an error. + * @throws IllegalAccessException + * in case of an error. + */ + private InspectITSamplerBase setSamplerContextInSampler(InspectITSamplerBase base, JavaSamplerContext context) throws NoSuchFieldException, SecurityException, IllegalArgumentException, + IllegalAccessException { + Field contextField = InspectITSamplerBase.class.getDeclaredField("context"); + contextField.setAccessible(true); + contextField.set(base, context); + return base; + } +} diff --git a/inspectITJMeter/test/info/novatec/inspectit/jmeter/localexecution/LocalRunner.java b/inspectITJMeter/test/info/novatec/inspectit/jmeter/localexecution/LocalRunner.java new file mode 100644 index 000000000..3665950bd --- /dev/null +++ b/inspectITJMeter/test/info/novatec/inspectit/jmeter/localexecution/LocalRunner.java @@ -0,0 +1,251 @@ +package info.novatec.inspectit.jmeter.localexecution; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import info.novatec.inspectit.jmeter.InspectITAggregatedSQL; +import info.novatec.inspectit.jmeter.InspectITAggregatedTimer; +import info.novatec.inspectit.jmeter.InspectITExceptionResult; +import info.novatec.inspectit.jmeter.InspectITGetConnectedAgents; +import info.novatec.inspectit.jmeter.InspectITHttpAggregation; +import info.novatec.inspectit.jmeter.InspectITHttpUsecaseAggregation; +import info.novatec.inspectit.jmeter.InspectITInvocationDetails; +import info.novatec.inspectit.jmeter.InspectITInvocationOverview; +import info.novatec.inspectit.jmeter.InspectITSamplerBase; +import info.novatec.inspectit.jmeter.InspectITSamplerBase.Configuration; +import info.novatec.inspectit.jmeter.data.AggregatedSQLResult; +import info.novatec.inspectit.jmeter.data.AggregatedTimerResult; +import info.novatec.inspectit.jmeter.data.ConnectedAgents; +import info.novatec.inspectit.jmeter.data.ExceptionResult; +import info.novatec.inspectit.jmeter.data.HttpAggregatedResult; +import info.novatec.inspectit.jmeter.data.HttpUsecaseResult; +import info.novatec.inspectit.jmeter.data.InvocationDetailResult; +import info.novatec.inspectit.jmeter.data.InvocationOverviewResult; +import info.novatec.inspectit.jmeter.util.XStreamFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.jmeter.config.Argument; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext; +import org.apache.jmeter.samplers.SampleResult; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Optional; +import org.testng.annotations.Parameters; +import org.testng.annotations.Test; + +/** + * Local test. This class can be used to run the JMeter sampler test locally against a remote CMR. + * By default the central CMR in NovaTec is used. The address of the CMR can be overwritten. + * + * Please understand, that this class will not be automatically be invoked. + * + * @author Stefan Siegl + */ +@SuppressWarnings("PMD") +// NOCHKALL +public final class LocalRunner { + + private String cmrIp; + private String cmrPort; + /** filled by the connectedAgent test case. */ + private ConnectedAgents agents; + + private Map> platformIdToInvocationDetails = new HashMap<>(); + private Map platformIdToArguments = new HashMap<>(); + + /** Constructor. */ + private LocalRunner() { + } + + @Parameters({ "ip", "port" }) + @BeforeTest + public void init(@Optional("172.16.145.234") String ip, @Optional("8182") String port) { + cmrIp = ip; + cmrPort = port; + } + + private Arguments buildBasicArguments() { + Arguments arguments = new Arguments(); + arguments.addArgument(new Argument(Configuration.HOST.name, cmrIp)); + arguments.addArgument(new Argument(Configuration.PORT.name, cmrPort)); + return arguments; + } + + private String readPlatformId(JavaSamplerContext ctx) { + return ctx.getParameter(Configuration.PLATFORM_ID.name); + } + + private Arguments cloneArgument(Arguments arguments) { + Arguments cloned = new Arguments(); + for (int i = 0; i < arguments.getArgumentCount(); i++) { + Argument arg = arguments.getArgument(i); + cloned.addArgument(new Argument(arg.getName(), arg.getValue())); + } + return cloned; + } + + @DataProvider(name = "ArgumentsWithAgent") + public Object[][] getArgumentsForConnectedAgents() { + Object[][] result = new Object[agents.getAgents().size()][1]; + for (int i = 0; i < agents.getAgents().size(); i++) { + Arguments arguments = buildBasicArguments(); + arguments.addArgument(new Argument(Configuration.PLATFORM_ID.name, "" + agents.getAgents().get(i).getPlatformId())); + result[i][0] = arguments; + } + return result; + } + + @DataProvider(name = "ArgumentsForInvocationDetail") + public Object[][] getArgumentsForInvocationDetails() { + int size = 0; + for (List values : platformIdToInvocationDetails.values()) { + size += values.size(); + } + + Object[][] result = new Object[size][2]; + + int i = 0; + for (String platformId : platformIdToArguments.keySet()) { + for (Long id : platformIdToInvocationDetails.get(platformId)) { + result[i][0] = cloneArgument(platformIdToArguments.get(platformId)); + result[i][1] = id; + i++; + } + } + + return result; + } + + @Test + public void connectedAgents() { + JavaSamplerContext context = new JavaSamplerContext(buildBasicArguments()); + InspectITSamplerBase testcase = new InspectITGetConnectedAgents(); + testcase.setupTest(context); + SampleResult result = testcase.runTest(context); + testcase.teardownTest(context); + + agents = (ConnectedAgents) XStreamFactory.getXStream().fromXML(result.getResponseDataAsString()); + + assertThat("No agents found, is your CMR running?", agents.getAgents(), is(not(empty()))); + } + + @Test(dataProvider = "ArgumentsWithAgent", dependsOnMethods = { "connectedAgents" }) + public void invocationSequence10(Arguments arguments) { + InspectITSamplerBase invocs = new InspectITInvocationOverview(); + arguments.addArgument(new Argument(Configuration.INVOC_COUNT.name, "10")); + arguments.addArgument(new Argument(Configuration.OUTPUT.name, "true")); + arguments.addArgument(new Argument(Configuration.INVOCATION_OVERVIEW_SORT.name, "TIMESTAMP")); + JavaSamplerContext context = new JavaSamplerContext(arguments); + + invocs.setupTest(context); + SampleResult result = invocs.runTest(context); + + InvocationOverviewResult invovOverviewResult = (InvocationOverviewResult) XStreamFactory.getXStream().fromXML(result.getResponseDataAsString()); + + assertThat("We did not find any invocation sequences, please check that there are some", invovOverviewResult.invocationIds, is(not(empty()))); + assertThat(result.isSuccessful(), is(true)); + + // provide the invocation details (that is the id of the invocation sequences) to other + // tests. + String platformId = readPlatformId(context); + platformIdToArguments.put(platformId, arguments); + platformIdToInvocationDetails.put(platformId, invovOverviewResult.invocationIds); + } + + @Test(dataProvider = "ArgumentsForInvocationDetail", dependsOnMethods = { "invocationSequence10" }) + public void invocationDetail(Arguments arguments, Long id) { + arguments.addArgument(new Argument(Configuration.INVOC_ID.name, id.toString())); + JavaSamplerContext context = new JavaSamplerContext(arguments); + + InspectITInvocationDetails invocDetails = new InspectITInvocationDetails(); + + invocDetails.setupTest(context); + SampleResult result = invocDetails.runTest(context); + + InvocationDetailResult detailResult = (InvocationDetailResult) XStreamFactory.getXStream().fromXML(result.getResponseDataAsString()); + + assertThat(detailResult, is(not(nullValue()))); + assertThat(result.isSuccessful(), is(true)); + } + + @Test(dataProvider = "ArgumentsWithAgent", dependsOnMethods = { "connectedAgents" }) + public void sql(Arguments arguments) { + JavaSamplerContext context = new JavaSamplerContext(arguments); + InspectITAggregatedSQL sql = new InspectITAggregatedSQL(); + sql.setupTest(context); + SampleResult result = sql.runTest(context); + sql.teardownTest(context); + + AggregatedSQLResult detailResult = (AggregatedSQLResult) XStreamFactory.getXStream().fromXML(result.getResponseDataAsString()); + + assertThat(detailResult, is(not(nullValue()))); + assertThat(result.isSuccessful(), is(true)); + } + + @Test(dataProvider = "ArgumentsWithAgent", dependsOnMethods = { "connectedAgents" }) + public void timer(Arguments arguments) { + JavaSamplerContext context = new JavaSamplerContext(arguments); + + InspectITAggregatedTimer timer = new InspectITAggregatedTimer(); + timer.setupTest(context); + SampleResult result = timer.runTest(context); + timer.teardownTest(context); + + AggregatedTimerResult detailResult = (AggregatedTimerResult) XStreamFactory.getXStream().fromXML(result.getResponseDataAsString()); + + assertThat(detailResult, is(not(nullValue()))); + assertThat(result.isSuccessful(), is(true)); + } + + @Test(dataProvider = "ArgumentsWithAgent", dependsOnMethods = { "connectedAgents" }) + public void exceptions(Arguments arguments) { + arguments.addArgument(new Argument(Configuration.EXCEPTION_SORT.name, "TIMESTAMP")); + JavaSamplerContext context = new JavaSamplerContext(arguments); + + InspectITExceptionResult exception = new InspectITExceptionResult(); + exception.setupTest(context); + SampleResult result = exception.runTest(context); + exception.teardownTest(context); + + ExceptionResult detailResult = (ExceptionResult) XStreamFactory.getXStream().fromXML(result.getResponseDataAsString()); + + assertThat(detailResult, is(not(nullValue()))); + assertThat(result.isSuccessful(), is(true)); + } + + @Test(dataProvider = "ArgumentsWithAgent", dependsOnMethods = { "connectedAgents" }) + public void httpAggregated(Arguments arguments) { + JavaSamplerContext context = new JavaSamplerContext(arguments); + + InspectITHttpAggregation http = new InspectITHttpAggregation(); + http.setupTest(context); + SampleResult result = http.runTest(context); + http.teardownTest(context); + + HttpAggregatedResult detailResult = (HttpAggregatedResult) XStreamFactory.getXStream().fromXML(result.getResponseDataAsString()); + + assertThat(detailResult, is(not(nullValue()))); + assertThat(result.isSuccessful(), is(true)); + } + + @Test(dataProvider = "ArgumentsWithAgent", dependsOnMethods = { "connectedAgents" }) + public void httpUsecase(Arguments arguments) { + JavaSamplerContext context = new JavaSamplerContext(arguments); + + InspectITHttpUsecaseAggregation http = new InspectITHttpUsecaseAggregation(); + http.setupTest(context); + SampleResult result = http.runTest(context); + http.teardownTest(context); + + HttpUsecaseResult detailResult = (HttpUsecaseResult) XStreamFactory.getXStream().fromXML(result.getResponseDataAsString()); + + assertThat(detailResult, is(not(nullValue()))); + assertThat(result.isSuccessful(), is(true)); + } +} diff --git a/inspectITJMeter/test/info/novatec/inspectit/jmeter/util/ResultServiceTest.java b/inspectITJMeter/test/info/novatec/inspectit/jmeter/util/ResultServiceTest.java new file mode 100644 index 000000000..8af83beec --- /dev/null +++ b/inspectITJMeter/test/info/novatec/inspectit/jmeter/util/ResultServiceTest.java @@ -0,0 +1,73 @@ +package info.novatec.inspectit.jmeter.util; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Mockito.when; + +import org.apache.jmeter.samplers.SampleResult; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import com.thoughtworks.xstream.XStream; + +@SuppressWarnings("PMD") +// NOCHKALL +public class ResultServiceTest { + + @Mock + XStream xStream; + + @BeforeTest + public void init() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void factory() { + ResultService service = ResultService.newInstance(); + assertThat(service, is(not(nullValue()))); + } + + @Test + public void normalUsage() { + ResultService service = ResultService.newInstance(); + + Object myPassedResult = new String(); + + final String xStreamResult = "Res"; + when(xStream.toXML(myPassedResult)).thenReturn(xStreamResult); + service.xStream = xStream; + + service.start(); + service.success(); + service.setResult(myPassedResult); + + SampleResult result = service.getResult(); + assertThat(result.isSuccessful(), is(true)); + assertThat(result.getResponseDataAsString(), is(equalTo(xStreamResult))); + } + + @SuppressWarnings("serial") + @Test + public void signalError() { + ResultService service = ResultService.newInstance(); + + final String exceptionMessage = "Res"; + + service.start(); + service.fail(new Exception() { + public String getMessage() { + return exceptionMessage; + } + }); + + SampleResult result = service.getResult(); + assertThat(result.isSuccessful(), is(false)); + assertThat(result.getResponseMessage(), is(equalTo(exceptionMessage))); + } +}

" + storageData.getDescription().substring(0, MAX_DESCRIPTION_LENGTH) + ".. [More]